00000000000000000000000000000001//111111111111111111111111111111110//minden bit komplementerét vesszük11111111111111111111111111111111//hozzáadtunk egyet, így megkaptuk a -1-et.[/quote]1.4 OperátorokA bitekkel mûveleteket végezhetünk, ehhez különbözõ operátorok állnak rendelkezésünkre. Lentebb minden operátort egyesével ki fogok fejteni, azt is, hogy mit csinál, és leírom az igazságtáblázatukat is. Konkrétan a következõ operátorok állnak rendelkezésünkre: Bitenkénti (bitek közötti) ÉSBitenkénti (bitek közötti) VAGYBitenkénti (bitek közötti) KIZÁRÓ VAGYBitenkénti (bitek közötti) NEGÁLÁSBiteltolás (arithmetic shift, shiftelés) operátor(ok)Logikai biteltolás (logical shift, logikai shiftelés) operátorAz elsõ négy felsorolt operátor tartozik a logikai operátorok közé, még az utolsó elõtti(igazából utolsó elõtti kettõ, mert tolhatjuk jobbra, illetve balra, azaz 2 shiftelõ operátor lesz), és az utolsó biteltolási mûvelet. Az operátorok a számok azonos helyi értékû bitjei között mûködnek, a számok minden egyes bitjére.1.4.1 Bitenkénti (bitek közötti) ÉS mûveletA bitenkénti ÉS mûvelet (AND, &) egy logikai operátor. Fontos, hogy nem összekeverendõ az \"&&\" logikai operátorral! Hasonlít rá, de ez a mûveleteket bitenként végzi. Ez az operátor csak akkor ad eredményül 1-et, ha mindkét bit 1, minden más esetben az eredmény 0. Igazság táblázata: A B A&B000100010111[/quote]Példának nézzük meg, mi történik, ha a bitenkénti ÉS operátort alkalmazzuk, a 11 és a 7 esetén. A 11 bináris számrendszerben 00000000000000000000000000001011-nek felel meg, a 7 pedig 00000000000000000000000000000111-nek. Tehát a 11&7, az ugyaz mint ha ezt írnám: 00000000000000000000000000001011 & 00000000000000000000000000000111. Az átláthatóság kedvéért most egymás alá fogom írni a 2 számot, hogy áttekinthetõbb legyen, és az eredményt pedig a 3. sorba: 00000000000000000000000000001011&0000000000000000000000000000011100000000000000000000000000000011[/quote]Eredményül tehát 00000000000000000000000000000011-et kaptunk, ami a kettes számrendszerben a 3-asnak felel meg. A következõ 2 sor tehát ugyanazt jelenti: Bin:00000000000000000000000000001011&00000000000000000000000000000111=00000000000000000000000000000011Dec:11&7=3[/quote]Még néhány példa, de ezeket már nem részletezem: 00000000000000000000000000000011 & 00000000000000000000000000000101 = 0000000000000000000000000000000100000000000000000000000001110110 & 00000000000000000000000001011001 = 00000000000000000000000001010000[/quote]1.4.2 Bitenkénti (bitek közötti) VAGY mûveletA bitenkénti VAGY mûvelet (OR, |) egy logikai operátor. Fontos, hogy nem összekeverendõ a \"||\" logikai operátorral! Hasonlít rá, de ez a mûveleteket bitenként végzi. Ez az operátor akkor ad eredményül 1-et, ha legalább az egyik bit 1. Ha egyik bit sem 1, akkor 0-t ad eredményül, ha mindkét bit 1, akkor is 1 az eredmény. Igazság táblázata: A B A|B000101011111[/quote]Példának nézzük meg, mi történik, ha a bitenkénti VAGY operátort alkalmazzuk, szintén a 11 és a 7 esetén. A 11 bináris számrendszerben mint már írtam 00000000000000000000000000001011-nek felel meg, a 7 pedig 00000000000000000000000000000111-nek. Tehát a 11 | 7, az ugyaz mint ha ezt írnám: 00000000000000000000000000001011 | 00000000000000000000000000000111. Az átláthatóság kedvéért ismét egymás alá fogom írni a 2 számot, hogy áttekinthetõbb legyen, és az eredményt pedig a 3. sorba: 00000000000000000000000000001011|0000000000000000000000000000011100000000000000000000000000001111[/quote]Eredményül tehát 00000000000000000000000000001111-et kaptunk, ami a kettes számrendszerben a 15-nekk felel meg. A következõ 2 sor tehát ugyanazt jelenti: Bin:00000000000000000000000000001011|00000000000000000000000000000111=00000000000000000000000000001111Dec:11|7=15[/quote]Megint néhány példa: 00000000000000000000000000000011 | 00000000000000000000000000000101 = 0000000000000000000000000000011100000000000000000000000001110110 | 00000000000000000000000001011001 = 00000000000000000000000001111111[/quote]1.4.3 Bitenkénti (bitek közötti) KIZÁRÓ VAGY mûveletA bitenkénti KIZÁRÓ VAGY mûvelet (XOR, ^) egy logikai operátor. Ez az operátor nagyon hasonlít a VAGY operátorra, az a fõ különbség a kettõ között, hogy amennyiben mindkét bit 1-es, akkor a KIZÁRÓ VAGY operátor 0-t ad eredményül, a többi esetben ugyanúgy viselkedik mint a VAGY operátor. Igazság táblázata: A B A^B000101011110[/quote]Példának nézzük meg, mi történik, ha a bitenkénti KIZÁRÓ VAGY operátort alkalmazzuk, szintén a 11 és a 7 esetén. A 11 bináris számrendszerben mint már írtam 00000000000000000000000000001011-nek felel meg, a 7 pedig 00000000000000000000000000000111-nek. Tehát a 11 ^ 7, az ugyaz mint ha ezt írnám: 00000000000000000000000000001011 ^ 00000000000000000000000000000111. Az átláthatóság kedvéért ismét egymás alá fogom írni a 2 számot, hogy áttekinthetõbb legyen, és az eredményt pedig a 3. sorba: 00000000000000000000000000001011^0000000000000000000000000000011100000000000000000000000000001100[/quote]Eredményül most 00000000000000000000000000001100-t kaptunk, ami a kettes számrendszerben a 12-nek felel meg. Érdekes, hogy teljesen más eredményt kapunk, pedig lényegében ugyanazt az eredményt adja az operátor, - kis különbséggel ugyebár - mint a VAGY operátor.A következõ 2 sor tehát ugyanazt jelenti: Bin:00000000000000000000000000001011^00000000000000000000000000000111=00000000000000000000000000001100Dec:11^7=12[/quote]Megint néhány példa: 00000000000000000000000000000011 ^ 00000000000000000000000000000101 = 0000000000000000000000000000011000000000000000000000000001110110 ^ 00000000000000000000000001011001 = 00000000000000000000000000101111[/quote]Ezzel kapcsolatban egy érdekességet mutatnék meg. Vegyünk 2 számot. A látványosság kedvéért én példának a 118-at és a 89-et veszem. 1. szám: 00000000000000000000000001110110 = 1182. szám: 00000000000000000000000001011001 = 89Mûvelet elvégzése: 00000000000000000000000001110110 ^ 00000000000000000000000001011001 = 00000000000000000000000000101111 = 471. szám az eredmény, 2. szám az eredeti 2. szám: 00000000000000000000000001011001 = 89Eredmény: 00000000000000000000000000101111 ^ 00000000000000000000000001011001 = 00000000000000000000000001110110 = 118[/quote]Vagyis visszakaptuk a kiindulási állapotot 1.4.4 Bitenkénti (bitek közötti) NEGÁLÁSA bitenkénti NEGÁLÁS (NOT, ~) az utolsó logikai operátor, melyet meg fogok mutatni. Ez az operátor eléggé eltér az eddigiektõl, leginkább abban, hogy ezt a mûveletet nem 2 bitre alkalmazzuk, hanem csak 1-re. Szokás komplementer-operátornak is hívni(konyhanyelven). Lényegében az 1-esekbõl 0-t, a 0-kból 1-est csinál. Igazság táblázata: A ~A0110[/quote]Példának nézzük meg, mi történik, ha a bitenkénti NEGÁLÁST alkalmazzuk a 7-es számra. A 7 bináris számrendszerben mint már írtam 00000000000000000000000000000111-nek felel meg. Tehát a ~7, az ugyaz mint ha ezt írnám: ~00000000000000000000000000000111. Nézzük tehát, mi történik: ~0000000000000000000000000000011111111111111111111111111111111000[/quote]Eredményül most 11111111111111111111111111111000-t kaptunk, ami a kettes számrendszerben a -8-nak felel meg. A következõ 2 sor tehát ugyanazt jelenti: Bin:~00000000000000000000000000000111=11111111111111111111111111111000Dec:~7=-8[/quote]Megint néhány példa: ~00000000000000000000000000000101 = 11111111111111111111111111111011~00000000000000000000000001110110= 11111111111111111111111110001010[/quote]Ehhez kapcsolódóan lenne itt egy fontos megjegyzendõ dolog. Aki nem érti, hogy miért -8 a megoldás, az nem olvasta el elég rendesen a \"Részletesebben a bitekrõl a Pawn-ban\" részt.1.4.5 Biteltolás (arithmetic shift, shiftelés) operátor(ok)A shift operátorok(<<, >>) a bitek tologatására használhatóak. A << balra tol, a >> jobbra. Bal oldalt található a szám, amit tologatni kívánunk, jobb oldalt pedig az, hogy mennyivel szeretnénk eltolni.1.4.5.1 Jobbra toló operátor(>>):Ez az operátor a szám bitjeit jobbra tolja a megadott mértékkel. Egy bittel való eltolás, 2-vel való osztásnak felel meg. Nézzünk egy példát: 00000000000000000000000000001000//8>>200000000000000000000000000000010//2[/quote]Láthatjuk, hogy semmi más nem történt, mint minden bitet 2-vel hátrébb mozgattunk. Az így \"megüresedett\" bitek helyére pedig 0-k kerültek. Lényegében az történt, hogy az utolsó 2 bit kiesett, az így megmaradt biteket 2-vel hátrébb csúsztattuk, majd az elsõ 2, így \"üres\" bit 0 értéket kapott.Nézzünk egy példát negatív számmal: 11111111111111111111111111110000//-8>>211111111111111111111111111111100//-2[/quote]Láthatjuk, hogy itt fontos szerepe van az MSB-nek. Ettõl függ ugyanis, hogy 0-k vagy 1-esek kerülnek a \"megüresedett\" bitek helyére. Fontos tehát megjegyezni, hogy a jobbra shiftelés megtartja a számok elõjelét, nem változtat azon!1.4.5.2 Balra toló operátor(<<):Ez az operátor a szám bitjeit balra tolja a megadott mértékkel. Egy bittel való eltolás, 2-vel való szorzásnak felel meg. Nézzünk egy példát: 00000000000000000000000000001000//8<<200000000000000000000000000100000//32[/quote]Itt megint könnyedén észrevehetõ mi is történt. Minden bitet fentebb \"toltunk\" 2-vel. Azok a bitek, amelyek ezáltal a 32. bit elé kerültek, kiesnek. Az így megüresedett bitek 0-kkal egészültek ki. Eddig nem túl bonyolult. Azonban figyeljünk oda, ha túl sokáig shiftelünk egy pozitív számot, akkor az utolsó érték, melyet fel tud venni a változónk (mielõtt még túlcsordulás miatt elérné a 0-t), az egy negatív szám!Nézzük mi történik negatív számok esetén: 11111111111111111111111111111000//-8<<211111111111111111111111111100000//-32[/quote]Láthatjuk hogy a balra shifteléskor a szám mindig 0-kkal egészítõdik ki, legyen az akár negatív, akár pozitív. Nem veszi figyelembe az MSB értékét, így ha túl sokat shiftelünk, akkor elõfordulhat, hogy a szám elveszti negatív értékét, és eléri a 0-t.1.4.6 Logikai biteltolás (logical shift, logikai shiftelés) operátorElõször is le kell tisztázni, hogy logikai shiftelésbõl csak egy létezik, jobbra logikai shiftelés(>>>). Sokban hasonlít a jobbra shiftelõ operátorra (>>), azonban van egy fontos különbség.Nézzük az elsõ példát: 00000000000000000000000000001000//8>>>200000000000000000000000000000010//2[/quote]Eddig nem igazán tapasztalhattunk semmi változást, minden pont ugyanúgy zajlott, mint a jobbra shiftelésnél(>>).Nézzük mi történik negatív szám esetén: 11111111111111111111111111111000//-8>>>200111111111111111111111111111110 //1073741822[/quote]Itt már láthatjuk a különbséget. Logikai shiftelés esetén az üres bitek helyére bekerülõ érték nem függ az MSB-tõl, hanem mindig 0. Emiatt soha nem kaphatunk eredményül negatív számot, logikai shiftelés esetén.1.5 Elsõ fejezet végeNos, ezennel átvettünk mindent, amit érdemes tudni a bitmûveletekrõl. Megmutattam az alapok, hogy hogyan épülnek fel a változók, valamint átvettük a fõ operátorokat. Eddig csak színtiszta elmélet volt. A következõ részben megmutatom, hogyan hasznosíthatjuk mindezt a gyakorlatban, vagyis mire is jó az a sok marhaság, amit eddig leírtam.2. Gyakorlati példák, érdekességek 2.1 Áttekintés, mikrõl is lesz még szóAz alábbiakban még pár dolgot szeretnék mutatni. Elõször is néhány \"függvénnyel\" fogok foglalkozni, majd mutatok még egy-két érdekességet. Konkrétan megmutatom, hogyan tudjuk egy jármû minden \"paneljének\" adatát kiíratni, részletesen(erre alapból nincs lehetõség, és a függvények is mind \"encode\" függvények, én viszont mutatok egy-egy decode \"verziót\" is.)Az alábbi 4 függvénnyel fogok foglalkozni: encode_tiresencode_panelsencode_doorsencode_lightsDe elõbb egy kis háttérinfó:A Pawnban a forrásban megadhatunk bináris számokat ugyanúgy, ahogyan Hex számokat is megadhatunk. Ehhez a \"0b\" prefixet kell használnunk. Tehát ha bináris számunk az 1011 és ezt akarjuk adni értékül egy változónak, azt így tehetjük meg: new var = 0b1011; 2.2 encode_tiresHáttér információ:A SA:MP-ban egy jármû abroncsainak adataid 4 bit tárolja. Az elsõ bit a jobb hátsó, a második bit a jobb elsõ, a harmadik bit a bal hátsó, a negyedik bit pedig a bal elsõ abroncsról tárol információt. Mivel 4 bitben eltárolhatóak az információk, így decimális formában 0-15 értékekkel is be lehet állítani a jármûvek abroncsait. 16-tól kezdve az értékek már ismétlõdni kezdenek, szóval a 16-nál az eredmény ugyanaz mint 0-nál, 17-nél ugyanaz mind 1-nél, és így tovább. Ez érthetõ, ha tudjuk hogyan épül fel a kettes számrendszer.(Ne felejtsük el, a biteket hátulról számozzuk!)Nos nézzük hogyan néz ki a kód: encode_tires(tire1, tire2, tire3, tire4){ return tire1 | tire2 << 1 | tire3 << 2 | tire4 << 3;} Láthatjuk, hogy bekérjük a 4 abroncs adatát(vagyis hogy ép[0], vagy kidurrant[1]), majd egy sima bitenkénti VAGY mûvelet és biteltolás kombinálásával visszatérünk az eredménnyel. A kódban a tire1 felel meg a jobb hátsó abroncsnak, a tire2 a jobb elsõ, a tire3 a bal hátsó, és a tire4 a bal elsõnek. Nézzünk egy konkrét példát. Tegyük fel, hogy én a hátsó 2 abroncsot ki akarom durrantani, még az elsõ kettõt épre akarom beállítani. Ez esetben így néz ki a dolog: encode_tire(1,0,1,0); Ha kiíratnánk konzolba a visszatérési értéket (%d-vel), 5-öt kapnánk. Miért is? Az 5 bináris alakja 0101. Hoppá, ha megnézzük ez pont fordítottja annak, amit mi megadtunk számjegyekként, csak már konkrét bináris számként. Nézzük hogyan is történhetett mindez, mit is csináltunk. A függvény a következõ mûveletet végzi: tire1 | tire2 << 1 | tire3 << 2 | tire4 << 3 Konyhanyelven úgy magyarázhatnánk el a dolgot, hogy ez a kód egymás mellé pakolgatja a megadott számokat, fordított sorrendben. Na de mi most nem a konyhanyelv miatt vagyunk ebben a leírásban, szóval nézzük a kódunkat. Lényegében ez futott le, a konkrét értékeinkkel nézve: 1 | 0 << 1 | 1 << 2 | 0 << 3 Bármily furcsa is, ez egy mûvelet, melyen szépen végig halad sorban a program. Elõször is írjuk át az egészet bináris alakba(az egyszerûség kedvéért most csak 4 bitet fogok kiírni): 0001 | 0000 << 1 | 0001 << 2 | 0000 << 3 Na erre se mondtuk volna rá a tutorial elsõ részének elolvasása elõtt, hogy ez egy mûveletsor. A rendszer elsõnek az eltolásokat végzi el (olyan ez mint a 2*3+2*4+2*5 mûveletben elõször a szorzásokat végezzük el, és csak utána adunk össze, ha nincs zárójel), és csak utána a bitenkénti VAGY mûveleteket, ezért mi is így fogunk tenni. Emlékezzünk, eltoláskor az \"üres\" bitek 0 értéket fognak felvenni. Végezzük el külön csak az eltolásokat: 0000 << 1 = 00000001 << 2 = 01000000 << 3 0000 Nos, ha ezeket elvégeztük, akkor nézzük, hogy is áll most a kódunk: 0001 | 0000 | 0100 | 0000 Négy azonos mûvelet van hátra, így hát a program innen más szépen sorban fog haladni. Tegyünk mi is így. Elsõ tehát a 0001 | 0000 mûvelet elvégzése. Emlékeztetõül, a bitenkénti VAGY mûvelet akkor ad 0-t eredményül, ha mind a kettõ érték 0, minden más esetben 1-et. Végezzük el külön az elsõ bitenkénti VAGY mûveletet: 0001 | 0000 = 0001 Nem volt bonyolult, nézzük tehát hogy áll akkor most a teljes mûveletünk: 0001 | 0100 | 0000 Haladunk szépen tovább, elvégezzük a 0001 | 0100 mûveletet. 0001 | 0100 = 0101 Nincs mit magyarázni rajta, egyszerû bitenkénti VAGY mûvelet volt ez is. Már csak ez van hátra: 0101 | 0000 Nos, ez sem lesz egy bonyolult mûvelet: 0101 | 0000 = 0101 Ezzel meg is kaptuk a végeredményt, ami nem más, mint a 0101 (tízes számrendszerben 5). Mivel azonban eléggé kusza lett így a számításunk, nézzük egyben hogyan is néz ki az egész, ha nem bontom ennyire részekre: 0100 | 0000 << 1 | 0001 << 2 | 0000 << 3v0001 | 0000 | 0100 | 0000v0001 | 0100 | 0000v0101 | 0000v0101 Nos, ezennel láthattuk a gyakorlatban is, hogyan mûködik a függvény. Kijelenthetjük tehát, hogy a függvény a számokat a sorrendjüknek megfelelõ számú bitbe helyezi. Mivel azonban a biteket hátulról számozzuk, így az 1,0,1,0 bemenetek végeredménye 0101 lesz. Így már nem is olyan bonyolult Na, ha már ilyen sokáig eljutottunk, akkor felhasználhatjuk ezt a kódot arra, hogy az alapjait figyelembe véve elkészítsük a decode változatot. Mivel 4 értékkel térünk vissza, ezért paraméterként kell majd 4 változó, melyeknek módosíthatjuk az értékét, illetve 1 paraméter pedig a dekódolandó szám. A függvényünk \"címsora\" tehát így néz ki: decode_tires(tires, &tire1, &tire2, &tire3, &tire4) Hogy példánk ne csak elmélet legyen, a tires változó legyen az elõbbi 0101 értékkel egyenlõ. Na akkor menjünk tovább. Bemeneti értékünk ugye a \"tires\" változóban lesz eltárolva, így ezzel fogjuk a mûveleteket végezni. Kezdjünk el gondolkozni. Hogyan is tudnánk ellenõrizni egy adatot. Legegyszerûbb ha a bitenkénti ÉS mûveletet alkalmazzuk. Miért is? Nos, a bitenkénti ÉS mûvelet ugye akkor ad eredményül 1-et, ha mind a 2 érték egy. Tehát ha min egy számot összehasonlítunk a 1-el(binárisan ugye 00000000000000000000000000000001, akkor az eredményünk lényegében arról fog tájékoztatni, hogy az elsõ bit 0, vagy 1. Nem is olyan bonyolult ez. Nézzük akkor az elsõ bitet. Arra emlékezhetünk még, hogy az elsõ bit a tire1, vagyis jobb hátsó abroncs adatait tárolja. Tehát: tire1 = tires & 1; Vagy konkrétan a példánk esetében: tire1 = 0101 & 0001 Mivel a bitenkénti ÉS mûvelet akkor ad 1-et eredményül, ha mind a kettõ bit 1, akkor már láthatjuk is, hogy miért elegendõ az elsõ bitet szemügyre venni. Mivel mind a 2 helyen az elsõ bit 1, ezért a tire1 változónk értéke is 1(0001) lesz. Egyszerû és nagyszerû, máris megkaptuk a jobb hátsó abroncs állapotát.Gondolkozzunk hát tovább. Nekünk most arra van szükségünk, hogy a 2. bit értékét megkapjuk. Hogyan kaphatjuk ezt meg? Legegyszerûbb megoldás, ha a tires változót jobbra shifteljük 1-el, majd ismételten bitenkénti ÉS mûveletet végzünk. Nézzük tehát, hogyan is nézne ez ki: tire2= tires >> 1 & 1; Vagy konkrétan a mi példánkban: tire2 = 0101 >> 1 & 0001vtire2 = 0010 & 0001 Ismételten bitenkénti ÉS mûvelet, és az eredményünk 0, mivel csak az egyik bit 1, a másik 0, így a bitenkénti ÉS mûvelet eredménye is 0. Ezen az elven megcsináljuk a maradék 2 abronccsal is: tire3= tires >> 2 & 1;tire4= tires >> 3 & 1; Vagy a mi esetünkben konkrét példánkkal: tire3 = 0101 >> 2 & 0001 // azaz: --> 0001 & 0001 = 1tire4 = 0101 >> 3 & 0001 // azaz: --> 0000 & 0001 = 0 A teljes kódunk tehát: decode_tires(tires, &tire1, &tire2, &tire3, &tire4){ tire1 = tires & 1; tire2 = tires >> 1 & 1; tire3 = tires >> 2 & 1; tire4 = tires >> 3 & 1;} A konkrét példánkban a 4 változónk értéke a függvény lefutása után: tire1: 1tire2: 0tire3: 1tire4: 0 Ha összehasonlítjuk, ezek pont azok az értékek, mint amiket az encode függvényben megadtunk, tehát úgy tûnik jól dolgoztunk 2.3 encode_lightsEz a rész lesz a legrövidebb. Az egész lámpás rész pontosan ugyanarra a sémára épül mint az abroncsok. Az adott bitek ugyanazon a \"helyen\" lévõ lámpa adatait szolgáltatják, ugyanúgy 0 az ép, és 1 a sérült lámpák állapot azonosítója, és ugyanúgy 1-1 bit tárolja az értékeket.Maga a kód: encode_lights(light1, light2, light3, light4){ return light1 | light2 << 1 | light3 << 2 | light4 << 3;} Láthatjuk hogy halál pontosan ugyanaz mint az encode_tires, csak más változónevekkel. Így hát a decode függvény is ugyan az lesz, csak a változóneveket kell módosítanunk: decode_lights(lights, &light1, &light2, &light3, &light4){ light1 = lights & 1; light2 = lights >> 1 & 1; light3 = lights >> 2 & 1; light4 = lights >> 3 & 1;} Ha megnézzük látjuk, hogy teljesen ugyan az a séma, ugyan az a felépítés, minden megegyezik, ezért nem is írtam le újból ugyanazt, amit egyszer már kifejtettem.2.4 encode_panelsHáttér információ:A SA:MP 7 panelrõl tárol adatokat. Egy panel adatait 4 bit írja le. Nos, ennél több infóra nekünk most nincs szükségünk, az elõzõ kód alapján ezen kód is megérthetõ, így az encode verzióhoz nem is nagyon szeretnék magyarázatot fûzni. A különbség az encode_tires-hez képest, hogy itt 4-essével shiftelünk, mivel 1 panel adatait 4 bit tárolja.Maga a kód: encode_panels(flp, frp, rlp, rrp, windshield, front_bumper, rear_bumper){ return flp | frp << 4 | rlp << 8 | rrp << 12 | windshield << 16 | front_bumper << 20 | rear_bumper << 24;} Nézzünk egy decode verziót, ehhez a kódhoz. Alapjaiban ugyanazon a vonalon kell elindulnunk, mint a decode_tires esetében. Itt most 7 visszatérési értékünk lesz, és 1 bemeneti érték. Tehát így néz ki a függvény header: decode_panels(panels, &flp, &frp, &rlp, &rrp, &windshield, &front_bumper, &rear_bumper) A folytatásban megint csak az elõbbi verziót kell alapul vennünk, azonban itt lesz egy kis különbség. Egyértelmû, hogy mivel 1 panel adatait 4 bit tárolja, így a 4 bitet nekünk egyben kell lekérni, és egyben kell vele visszatérnünk. Megint csak a bitenkénti ÉS mûveletet fogjuk alkalmazni, azonban mivel 4 bitrõl van szó, így egy olyan számmal kell összehasonlítanunk, mely bináris alakban 1111. Ez a szám nem más, mint a 15. Hogy miért ez kell?Ha egy bitsorozat tartalmát ki szeretnénk másolni egy másik változóba, akkor a bitenkénti ÉS mûveletre vna szükségünk. A bitenkénti ÉS mûvelet akkor ad 1-et eredményül, ha mindkét bit 1, ha valamelyik 0, akkor a visszatérés már 0 lesz. Ergó tehát, ha egy csupa 1-esekbõl álló bitsorozattal vetjük össze az eredeti bitsorozatunkat, akkor lényegében lemásoljuk a sorozatot, egy másik változóba, hisz eredményül mindig az eredeti sozatot fogjuk visszakapni. Íme néhány példa: 0101 & 1111 = 01011111 & 1111 = 11110000 & 1111 = 00001011 & 1111 = 1011 Láthatjuk tehát, hogy ha csupa 1-est tartalmazó bitsorozattal hasonlítjuk össze az eredeti számunkat, akkor lényegében egy másolatot készítünk. Mivel nekünk erre a 4 bitre van szükségünk a 32-bõl, így pontosan ezt kell tennünk, ezen 4 bitet lemásolni egy másik változóba, hogy késõbb ezt majd egy másik függvénnyel még jobban darabokra szedhessük.Íme tehát akkor a kódunk elsõ sora: flp = panels & 15; Mivel 4-esével vizsgáljuk a biteket, így lényegében annyit kell még változtatnunk az elõzõ kódon(decode_tires), hogy az 1-esével növekvõ shiftelések helyett 4-esével shifteljünk. Azaz a 2. panel adatainak lekérdezése: frp = panels >> 4 & 15; Innen pedig már csak mindig további 4 shiftelést el kell végeznünk, és szépen végig haladhatunk a panels változó bitjein, és megkaphatjuk minden panel adatait külön-külön. rlp = panels >> 8 & 15;rrp = panels >> 12 & 15;windshield = panels >> 16 & 15;front_bumper= panels >> 20 & 15;rear_bumper = panels >> 24 & 15; Ezennel minden panel adatait megkaptuk. Ezután már akár a decode_tires függvényt felhasználva szét lehet szedni az adatokat darabokra, de akár egy saját névvel, és más változó nevekkel ellátott másik függvényt is létre lehet hozni a decode_tires mintájára.Tehát az egész kód egyben: decode_panels(panels, &flp, &frp, &rlp, &rrp, &windshield, &front_bumper, &rear_bumper){ flp = panels & 15; frp = panels >> 4 & 15; rlp = panels >> 8 & 15; rrp = panels >> 12 & 15; windshield = panels >> 16 & 15; front_bumper= panels >> 20 & 15; rear_bumper = panels >> 24 & 15;} Igazából a függvény ugyanarra a sémára épül mint a decode_tires, csak annyi a különbség, hogy 4 bitenként kapjuk az adatokat, nem pedig egyesével, mivel itt minden panelnek 4 adatát tudjuk majd még lekérdezni. Nem tudom lehet-e még ehhez a részhez hozzáfûzni valamit, szóval szerintem ugorjunk. 2.5 encode_doorsHáttér információ:A SA:MP képes minden jármû 4 \"ajtajának\" állapotát tárolni. Ezek az \"ajtók\"(Doors-t hogy fordíthatnám jobban?) konkrétan a motorháztetõ, csomagtartó, vezetõoldali ajtó, és anyósülés oldali ajtó. A 2 hátsó ajtó állapotát jelenleg még nincs lehetõségünk lekérdezni.Amikor lekérdezzük egy jármû \"ajtajának\" adatait, azt a SA:MP 4 bájtként(32 bit) adja vissza. Ebbõl a 4 bájtból 1 bájt(8 bit) jut minden egyes objektumra. Vagyis minden egyes objektum adatait 8 bit tárolja. Vagyis tárolhatná...A SA:MP csak az elsõ 3 bitet használja, a további bitek üresek lesznek, nincs funkciójuk, talán majd késõbb lesz. Az elsõ bit azt tárolja, hogy az objektum zárt[0], vagy nyitott[1](nem az jelenti, hogy bezárt, hanem becsukott/kinyitott) állapotban van-e. A második bit azt adja meg, hogy az objektum ép[0] vagy sérült[1]. A 3. bit arról tájékoztat, hogy az adott objektum rajta van-e még a jármûvön[0] vagy már leesett[1]. A többi bit mint mondtam üres. Ez majd a decode függvénynél lesz fontos.Nézzük tehát a konkrét kódot: encode_doors(bonnet, boot, driver_door, passenger_door, behind_driver_door, behind_passenger_door){ #pragma unused behind_driver_door #pragma unused behind_passenger_door return bonnet | boot << 8 | driver_door << 16 | passenger_door << 24;} A 2 hátsó ajtóval nem foglalkozunk, mivel azok jelenleg még nem támogatottak ugyebár, de a kód készítõje gondolt a jövõre is, vagy fogalmam sincs miért adta meg ezt a két paramétert is, hisz ígyis-úgyis módosítani kell a kódon... Ha behoznak még 2 ajtót, akkor az 1 bájtos tárolással már 6 bájt lenne, de 1 változó maximum 4 bájt lehet, tehát a kódot úgyis módosítani kell, ha meg arra gondoltak, hogy legalább a paramétereket nem kell majd szegény scriptereknek minden sorban átírni, az megint hülyeség, hisz ki adna értéket olyan paraméternek, amely nincs használva?...Nos mindegy, nem a kritizálásért vagyunk most itt. Ha megnézzük, a kód 8-as shiftelést használ, mivel egy objektum adatait 8 bit tárolja. A mûködési elv ugyanaz, mint az elõzõ encode függvényeknél, így erre nem kívánok megint kitérni.Nézzük azonban hogyan írnánk ehhez egy decode függvényt. Mikor megkérdeztem errõl ismerõsömet, õ azt mondta, ugyanúgy mint az elõzõ, decode_panels függvénynél, csak 8-as shiftelést használunk, és 15 helyett 255-el végezzük a bitenkénti ÉS mûveletet. 255-el? minek?No erre is ki fogok térni késõbb, de haladjunk a szokásos módon. 1 bemeneti paraméterünk van, és 4 kimeneti, vagyis a header rész a következõ: decode_doors(doors, &bonnet, &boot, &driver_door, &passenger_door) Ezzel meg is vagyunk, itt nem látok különösebb magyarázni valót. Én most nem fogok törõdni a 2 nem létezõ ajtóval, ha egy jövõbeni verzióban netán azokat is behoznák mint lekérdezhetõ objektum, akkor ígyis-úgyis módosítani kell majd a kódot, addig meg úgysincs rá szükségünk.Nos, mint az eddigi decode függvényeknél, elsõ feladatunk egy shiftelés mentes bitenkénti ÉS mûvelet elvégzése. Az egyik megoldás a következõ: bonnet = doors & 255; Lehet így is. Azonban véleményem szerint felesleges itt egy ekkora számmal \"szenvednünk\". Miért használjunk ekkora számot, és ezáltal bonyolítsuk feleslegesen a kódunkat, ha nekünk a 8 bitbõl csak az elsõ 3 lesz ami adatot tárol? Bõven elegendõ egy olyan szám, amely bináris alakja 111. Ez nem más, mint a 7. Tehát a következõ megoldás ugyanúgy helyes: bonnet = doors & 7; A két kód pontosan ugyanazt az eredményt fogja visszaadni, semmi különbség a kettõ között. Csupán elsõ esetében nagy számmal dolgozunk, még második verzió esetén csak akkorával, amekkora szükséges.A kód többi részében csak 8-as shifteléssel végigcammogunk a doors változó többi bitjén is: boot = doors >> 8 & 7;driver_door = doors >> 16 & 7;passenger_door = doors >> 24 & 7; A teljes kód egyben: decode_doors(doors, &bonnet, &boot, &driver_door, &passenger_door){ bonnet = doors & 7; boot = doors >> 8 & 7; driver_door = doors >> 16 & 7; passenger_door = doors >> 24 & 7;} A kód pont ugyanazon az elven épül fel, mint az összes többi decode függvényünk, csak épp a benne használatos értékek az \"ajtó\" objectumok adatstruktúrájához vannak igazítva.2.6 Egyéb ötletek gyakorlati alkalmazásra, hasznosításraJoggal felmerülhet a kérdés, hogy ennyi? Emiatt rágtuk végig magunkat ezen néhány tízezer karakteren? Nem. Nem ennyi. A SA:MP-ban rengeteg felhasználási módja lehet még a bitmûveleteknek. Mutatok is néhány dolgot, mire jók még.2.6.1 RGBA->HEX; HEX->RGBA konvertálásokNos, bármily \"hihetetlen\", erre is használható. Miért is? Nos, 16-os számrendszerben 0-9, valamint A-F \"szánjegyek\" léteznek. A hexadecimális számjegy decimális értékben 0-15 lehet. Ha megnézzük bináris értékeket, 0-15 az 4 bittel leírható, hiszen a 15 az bináris alakban 1111. De ha józan paraszti ésszel belegondolunk is. 16 az 24. Így tehát 1 hexadecimális érték, az 4 bináris értékkel írható le. Ebbõl már sejthetõ, hogy itt is shiftelésre lesz szükségünk. Naná Mivel 4 bit ír le egy hexadecimális számjegyet, így 4-esével kell majd shiftelnünk.Mi is az az RGBA?RGBA: A színek olyan meghatározási módja, melyben a 3 alapszín([r]ed, [g]reen, lue) mellett egy 4. érték, az úgynevezett [a]lpha is szerepel, amely az áttetszõséget adja meg. A legtöbb program(pl. paint, PS), és pl. a css(külön függvénnyel!) is elfogadja ezen értékeket decimális számokként(0-255) is, a SA:MP-ban azonban hexadecimális alakban kell megadnunk. Bármily furcsa a következõ 4 sor pont ugyan azt jelenti:\"Normál\" RGB alak(pl. paint, photoshop): Piros: 0, Zöld: 255, Kék: 255CSS alak(függvénnyel): rgba(0, 255, 255, 1);Pawn alak(1): 0x00FFFFFFPawn alak(2): 16777215 Ó igen, a Pawn-ban decimális számokként is megadhatjuk a színeket, hisz a program mindent bináris számrendszerbe fog átszámolni. De akár bináris alakban is megadhattuk volna:Pawn alak(3): 0b111111111111111111111111 Az, hogy mi a Pawn-nak hexadecimális alakban szoktuk megadni, annak az egyetlen oka, hogy így a leginkább \"átlátható\", és ez egy amúgy is elfogadott módja (mármint a hexadecimális számmal való megadás) a színek leírásának a számítástechnikában.Nos, mivel 1 szín 0 és 255 közötti értéket tartalmaz a piros, zöld és kék színekbõl, ez hexadecimálisan 2 számjeggyel leírható: 00-FF. Az FF értéke 255, aki nem hiszi, számoljon utána (162-1)2 hexadecimális számjegy az pedig 8 bináris számjeggyel leírható(00000000-11111111). Így már láthatjuk, hogy 8-as shiftelésre lesz szükségünk. Másra nincs is szükségünk, az RGBA->HEX konverter elkészítéséhez.stock RGBAToHex( r, g, b, a ){return a | b << 8 | g << 16 | r << 24;} Az, hogy miért fordított sorrendben kellett megadnunk az értékeket, az megint csak azzal magyarázható, hogy a biteket hétköznapi értelemben véve visszafelé számozzuk.Egy ellentét, azaz HexToRGBA függvény megírása sem túl bonyolult, ugyan ezt kell csinálnunk, csak visszafelé.stock HexToRGBA( colour, &r, &g, &b, &a ){a = colour & 0xFF;b = (colour >> 8 ) & 0xFF;g = (colour >> 16 ) & 0xFF;r = (colour >> 24 ) & 0xFF;} A 0xFF-et természetesen decimális értékkel is megadhatjuk(255).No akkor lehet ugye RGBA-t ARGB-be is konvertálni, az így néz ki:RGBAToARGBHEX(r,g,b,a){return (r & 0xff) << 24 | (g & 0xff) << 16 | (b & 0xff) << 8 | a & 0xff;} Ez máris egy újabb felhasználási módja a bitmûveleteknek. De, mutatok még 2.6.2 Boolen változók helyett IntegerAz al-al-alcímet elolvasva joggal felmerülhet a kérdés, hogy ez meg hogy jön ide. Nos ez úgy jön ide, hogy most egy kis memóriatakarékosságról fogok itt regélni. Adhattam volna azt is címnek, hogy miért ne használjunk boolean változókat, ha legalább 5 igaz/hamis értéket el akarunk tárolni.Nos akkor egy kis memórialecke. Azt már tisztáztuk, hogy az integer változók 32 bitesek, vagyis 4 bájtot foglalnak le a memóriában maguknak. A boolean típusú változókról azonban még nem volt szó. Mivel 1 értéket tárolnak csak, az is csak igaz vagy hamis lehet, így joggal gondolhatnánk, hogy 1 bit a mérete. Azonban nem...1 boolean változó 8 bitet, azaz 1 bájtot foglal le magának a memóriában. Hogy ez miért így van, az a számítógépek memóriahasználatával magyarázható. Manapság nincs mód arra, hogy a memóriában egyetlen bitet módosítsunk, csak 8-as \"csomagokban\", vagyis bájtonként tudjuk ezt megtenni. Hogy ez miért van így, azt most nem fogom itt fejtegetni Nos, ha megnézzük tehát, a boolean változó egy nagyon-nagyon pazarló megoldás. Hiszen 1 darab boolean változó 1 bájtot foglal, 4 darab boolean változó esetén ez már 4 bájt, ami megegyezik 1 integer változó méretével. Azonban, még a 4 boolean változóban 4 igaz/hamis érték kerülhet tárolásra, addig egy integerben 32 igaz/hamis értéket is eltárolhatunk.Bizony, 32 igaz/hamis érték. Hiszen 1 integer változó 32 bitbõl áll, ha minden bitet hasznosítunk, akkor a bitmûveletek felhasználásával 32 igaz/hamis értéket is eltárolhatunk. Összehasonlítva:32 darab boolean változó mérete 32 bájt.1 integer változó mérete: 4 bájt. Vagyis 32 boolean változó esetén nyolcad annyi memóriánkba kerül, ha egy darab integerben tároljuk el az értékeket. Persze 32 booleanra ritkán van szükség, de egy RP modban simán elõfordulhat. Gondoljunk csak bele... Van 32 boolean-tömbös változónk, ez a maximum slotszámot(1000) figyelembe véve: 32*1000 byte azaz 32000 byte ami ~31 kilobyte. Ha ezt egyetlen integer-tömbbel oldanánk meg: 4*1000 byte, azaz 4000 byte, ami ~3,9 kilobyte. Azért így nézve már nem annyira \"elhanyagolandó\" az a különbség szerintem =)Akár írhatnánk erre külön egy függvényt, mely az adott bit értékével tér vissza. Nem is bonyolult. Így már azok számára is használhatóak a bitmûveletek, akik nem értenek hozzá, és rengeteg memóriát takaríthatnak meg.Íme egy függvény az adatok kiolvasására:stock GetData(integer, bit){ return integer >> bit & 1; //vagy az 1 helyett akár 0x01 alakban is megadhatjuk, nincs jelentõsége} És a párja, adat írásra:stock SetData(&integer, bit, value){ integer |= value << bit; return 1;} Persze ezt még lehetne csinosítani, hogy pl. ne 0-tól hanem 1-tõl számoljuk a biteket, vagy hogy 31(vagy 32 ha 1-tõl számolunk) felett 0 legyen a return értéke, esetleg a value-re is rakhatnánk ellenõrzést, hogy 0-1-e az értéke, vagy más, de ilyenekkel most nem törõdtem.2.6.3 Több érték, egy integerbeNa ez az a rész, ami véleményem szerint már csak a nagyon megrögzött memóriafelszabadítók szerint nem lesz felesleges Amennyiben nincs szükségünk túl nagy számokra(pl. csak 0-15, vagy akár csak 0-7, de akár még a 0-255 is megfelel), akkor akár egy integerbe is tehetjük õket. Hogyan? Egyszerû. Csoportosítjuk a biteket. Tegyük fel, hogy maradván egy RP szervernél, el akarjuk tárolni ûbergigantikusan memóriakímélõ megoldással a játékos házának számát(ez legyen mondjuk 0-100 közötti szám), a játékos frakcióját(ez legyen 0-15 között), a játékos rangját(0-7 között), hogy a telefonja be van-e kapcsolva(0-1), a játékos szolgálati jármûvének id-jét(legyen 0-255 között), és az alapmunkáját(0-15 között). Ez is megoldható, egyetlen integerben, és ismételten 1000 slottal számolva máris megspóroltunk 4*4*1000+1*1000 byte-t(5 integer, és 1 boolean helyett csupán 1 integert használunk), ami ~16,6 kilobyte. Azért nem mindegy az, hogy 20,5 kb-ot vagy 4 kb-ot használunk szerintem... Ezt a következõ módszerrel valósíthatnánk meg:#define DATA_HOUSE 0#define DATA_FACTION 1#define DATA_RANK 2#define DATA_PHONESTATE 3#define DATA_DUTYCAR 4#define DATA_JOB 5stock GetData(playerinfo, data){ switch(data) { case DATA_HOUSE: return playerinfo & 127;//7 bit case DATA_FACTION: return playerinfo >> 7 & 15;//4 bit case DATA_RANK: return playerinfo >> 11 & 7;//3 bit case DATA_PHONESTATE: return playerinfo >> 14 & 1;//1 bit case DATA_DUTYCAR: return playerinfo >> 15 & 255;//8 bit case DATA_JOB: return playerinfo >> 23 & 15;//4 bit}return -1;}stock SetData(&playerinfo, data, value){ switch(data) { case DATA_HOUSE: { playerinfo = playerinfo & ~127 | value; }//7 bit (0-127 közötti maximálisan) case DATA_FACTION: { playerinfo = playerinfo & ~(15 << 7) | value << 7; }//4 bit (0-15 között) case DATA_RANK: { playerinfo = playerinfo & ~(7 << 11) | value << 11; }//3 bit (0-7 között) case DATA_PHONESTATE: { playerinfo = playerinfo & ~(1 << 14) | value << 14; }//1 bit (0-1) case DATA_DUTYCAR: { playerinfo = playerinfo & ~(255 << 15) | value << 15; }//8 bit (0-255 között) case DATA_JOB: { playerinfo = playerinfo & ~(15 << 23) | value << 23; }//4 bit (0-15 között) } return -1;}stock SetDatas(&playerinfo, house, faction, rank, phonestate, dutycar, job){playerinfo |= house | (faction << 7) | (rank << 11) | (phonestate << 14) | (dutycar << 15) | (job << 23);} Ez már azért elég morbid, ezt inkább csak érdekességképp mutattam, de éppenséggel ez is használható lenne, csak ehhez már kell egy kis õrültség 2.7 Második fejezet végeNos, láthattuk hogyan is mûködik mindazon sok \"marhaság\", amit az elsõ fejezetben elméletként átvettünk. Láthattuk hogyan mûködnek a jármûvek paneljének adataival babráló encode függvények, valamint megmutattam, hogyan írhatunk hozzájuk decode verziókat. Láthattunk még példát egyéb gyakorlati hasznosításra, kicsit megkoccoltuk a hexadecimális számok világát is, majd szóba került egy kis memóriatakarékosság. Végül mutattam egy összetettebb, elvetemült példát is, a bitmûveletek hasznosítására.3. Összegzés, végszó Úgy gondolom, hogy elég részletesen körbe jártuk a témát. Megnéztük a bitmûveletek elméleti, és gyakorlati oldalát is, láthattunk gyakorlati példákat. Nem kell elsõre megérteni ezt az egészet, sõt... Elegendõ ha egyszer elolvassuk, emésztjük kicsit, majd késõbb megint elõvesszük, akkor netán már kicsit többet is megértünk belõle. Nem egy kihagyhatatlan része a Pawno-nak, de azért van annak haszna, ha az ember jártas ebben a témában is, bár sokak szerint nem éri meg a sok fáradtságot, és idõt, mely a megértéséhez, begyakorlásához, használatának elsajátításához szükséges. Próbáltam mindent részletesen kifejteni. A bonyolultabb dolgokhoz sokat magyarázni, az egyszerûbbek esetén csak visszautalni az alapokhoz. Sok dolgot talán már túl is magyaráztam. Ezt a leírást nem 1 nap alatt készítettem el, kb. 1 hónapja írtam meg az elméleti oldalát, most az elmúlt pár napban pedig a gyakorlatot. Esténként 1-2 órát szántam rá csupán, és mivel ilyenkor már nem vagyok épp a toppon, elképzelhetõ, hogy néhány helyen helyesírási, vagy elvi hibát vétettem. Amennyiben valaki ilyet találna, kérem szóljon, és korrigálni fogom. Nos, azt hiszem nincs is más hátra, mint hogy sok szerencsét kívánjak a bitmûveletek elsajátításához, és megosszam a forrásokat. Remélem néhányan hasznosnak találják, eme igen hosszúra sikerült leírást.4. Források: Internet:WikipédiaSA:MP WikiSA:MP hivatalos fórumKönyv, e-book:Pohl László - A programozás alapjai(BME - Elektronikus Eszközök Tanszéke, Budapest, 2010)Siroki László - C programozás - Bitmûveletek jegyzeteˆ Anthony, 2012 - Minden jog fenntartva.A leírás egészben, vagy részleteiben szabadon terjeszthetõ, ameddig egyértelmûen fel van tüntetve az eredeti készítõ, valamint hogy honnan is származik. « Utoljára szerkesztve: 2012. augusztus 01. - 10:53:54 írta Anthony » Naplózva
A B A&B000100010111[/quote]Példának nézzük meg, mi történik, ha a bitenkénti ÉS operátort alkalmazzuk, a 11 és a 7 esetén. A 11 bináris számrendszerben 00000000000000000000000000001011-nek felel meg, a 7 pedig 00000000000000000000000000000111-nek. Tehát a 11&7, az ugyaz mint ha ezt írnám: 00000000000000000000000000001011 & 00000000000000000000000000000111. Az átláthatóság kedvéért most egymás alá fogom írni a 2 számot, hogy áttekinthetõbb legyen, és az eredményt pedig a 3. sorba: 00000000000000000000000000001011&0000000000000000000000000000011100000000000000000000000000000011[/quote]Eredményül tehát 00000000000000000000000000000011-et kaptunk, ami a kettes számrendszerben a 3-asnak felel meg. A következõ 2 sor tehát ugyanazt jelenti: Bin:00000000000000000000000000001011&00000000000000000000000000000111=00000000000000000000000000000011Dec:11&7=3[/quote]Még néhány példa, de ezeket már nem részletezem: 00000000000000000000000000000011 & 00000000000000000000000000000101 = 0000000000000000000000000000000100000000000000000000000001110110 & 00000000000000000000000001011001 = 00000000000000000000000001010000[/quote]1.4.2 Bitenkénti (bitek közötti) VAGY mûveletA bitenkénti VAGY mûvelet (OR, |) egy logikai operátor. Fontos, hogy nem összekeverendõ a \"||\" logikai operátorral! Hasonlít rá, de ez a mûveleteket bitenként végzi. Ez az operátor akkor ad eredményül 1-et, ha legalább az egyik bit 1. Ha egyik bit sem 1, akkor 0-t ad eredményül, ha mindkét bit 1, akkor is 1 az eredmény. Igazság táblázata: A B A|B000101011111[/quote]Példának nézzük meg, mi történik, ha a bitenkénti VAGY operátort alkalmazzuk, szintén a 11 és a 7 esetén. A 11 bináris számrendszerben mint már írtam 00000000000000000000000000001011-nek felel meg, a 7 pedig 00000000000000000000000000000111-nek. Tehát a 11 | 7, az ugyaz mint ha ezt írnám: 00000000000000000000000000001011 | 00000000000000000000000000000111. Az átláthatóság kedvéért ismét egymás alá fogom írni a 2 számot, hogy áttekinthetõbb legyen, és az eredményt pedig a 3. sorba: 00000000000000000000000000001011|0000000000000000000000000000011100000000000000000000000000001111[/quote]Eredményül tehát 00000000000000000000000000001111-et kaptunk, ami a kettes számrendszerben a 15-nekk felel meg. A következõ 2 sor tehát ugyanazt jelenti: Bin:00000000000000000000000000001011|00000000000000000000000000000111=00000000000000000000000000001111Dec:11|7=15[/quote]Megint néhány példa: 00000000000000000000000000000011 | 00000000000000000000000000000101 = 0000000000000000000000000000011100000000000000000000000001110110 | 00000000000000000000000001011001 = 00000000000000000000000001111111[/quote]1.4.3 Bitenkénti (bitek közötti) KIZÁRÓ VAGY mûveletA bitenkénti KIZÁRÓ VAGY mûvelet (XOR, ^) egy logikai operátor. Ez az operátor nagyon hasonlít a VAGY operátorra, az a fõ különbség a kettõ között, hogy amennyiben mindkét bit 1-es, akkor a KIZÁRÓ VAGY operátor 0-t ad eredményül, a többi esetben ugyanúgy viselkedik mint a VAGY operátor. Igazság táblázata: A B A^B000101011110[/quote]Példának nézzük meg, mi történik, ha a bitenkénti KIZÁRÓ VAGY operátort alkalmazzuk, szintén a 11 és a 7 esetén. A 11 bináris számrendszerben mint már írtam 00000000000000000000000000001011-nek felel meg, a 7 pedig 00000000000000000000000000000111-nek. Tehát a 11 ^ 7, az ugyaz mint ha ezt írnám: 00000000000000000000000000001011 ^ 00000000000000000000000000000111. Az átláthatóság kedvéért ismét egymás alá fogom írni a 2 számot, hogy áttekinthetõbb legyen, és az eredményt pedig a 3. sorba: 00000000000000000000000000001011^0000000000000000000000000000011100000000000000000000000000001100[/quote]Eredményül most 00000000000000000000000000001100-t kaptunk, ami a kettes számrendszerben a 12-nek felel meg. Érdekes, hogy teljesen más eredményt kapunk, pedig lényegében ugyanazt az eredményt adja az operátor, - kis különbséggel ugyebár - mint a VAGY operátor.A következõ 2 sor tehát ugyanazt jelenti: Bin:00000000000000000000000000001011^00000000000000000000000000000111=00000000000000000000000000001100Dec:11^7=12[/quote]Megint néhány példa: 00000000000000000000000000000011 ^ 00000000000000000000000000000101 = 0000000000000000000000000000011000000000000000000000000001110110 ^ 00000000000000000000000001011001 = 00000000000000000000000000101111[/quote]Ezzel kapcsolatban egy érdekességet mutatnék meg. Vegyünk 2 számot. A látványosság kedvéért én példának a 118-at és a 89-et veszem. 1. szám: 00000000000000000000000001110110 = 1182. szám: 00000000000000000000000001011001 = 89Mûvelet elvégzése: 00000000000000000000000001110110 ^ 00000000000000000000000001011001 = 00000000000000000000000000101111 = 471. szám az eredmény, 2. szám az eredeti 2. szám: 00000000000000000000000001011001 = 89Eredmény: 00000000000000000000000000101111 ^ 00000000000000000000000001011001 = 00000000000000000000000001110110 = 118[/quote]Vagyis visszakaptuk a kiindulási állapotot 1.4.4 Bitenkénti (bitek közötti) NEGÁLÁSA bitenkénti NEGÁLÁS (NOT, ~) az utolsó logikai operátor, melyet meg fogok mutatni. Ez az operátor eléggé eltér az eddigiektõl, leginkább abban, hogy ezt a mûveletet nem 2 bitre alkalmazzuk, hanem csak 1-re. Szokás komplementer-operátornak is hívni(konyhanyelven). Lényegében az 1-esekbõl 0-t, a 0-kból 1-est csinál. Igazság táblázata: A ~A0110[/quote]Példának nézzük meg, mi történik, ha a bitenkénti NEGÁLÁST alkalmazzuk a 7-es számra. A 7 bináris számrendszerben mint már írtam 00000000000000000000000000000111-nek felel meg. Tehát a ~7, az ugyaz mint ha ezt írnám: ~00000000000000000000000000000111. Nézzük tehát, mi történik: ~0000000000000000000000000000011111111111111111111111111111111000[/quote]Eredményül most 11111111111111111111111111111000-t kaptunk, ami a kettes számrendszerben a -8-nak felel meg. A következõ 2 sor tehát ugyanazt jelenti: Bin:~00000000000000000000000000000111=11111111111111111111111111111000Dec:~7=-8[/quote]Megint néhány példa: ~00000000000000000000000000000101 = 11111111111111111111111111111011~00000000000000000000000001110110= 11111111111111111111111110001010[/quote]Ehhez kapcsolódóan lenne itt egy fontos megjegyzendõ dolog. Aki nem érti, hogy miért -8 a megoldás, az nem olvasta el elég rendesen a \"Részletesebben a bitekrõl a Pawn-ban\" részt.1.4.5 Biteltolás (arithmetic shift, shiftelés) operátor(ok)A shift operátorok(<<, >>) a bitek tologatására használhatóak. A << balra tol, a >> jobbra. Bal oldalt található a szám, amit tologatni kívánunk, jobb oldalt pedig az, hogy mennyivel szeretnénk eltolni.1.4.5.1 Jobbra toló operátor(>>):Ez az operátor a szám bitjeit jobbra tolja a megadott mértékkel. Egy bittel való eltolás, 2-vel való osztásnak felel meg. Nézzünk egy példát: 00000000000000000000000000001000//8>>200000000000000000000000000000010//2[/quote]Láthatjuk, hogy semmi más nem történt, mint minden bitet 2-vel hátrébb mozgattunk. Az így \"megüresedett\" bitek helyére pedig 0-k kerültek. Lényegében az történt, hogy az utolsó 2 bit kiesett, az így megmaradt biteket 2-vel hátrébb csúsztattuk, majd az elsõ 2, így \"üres\" bit 0 értéket kapott.Nézzünk egy példát negatív számmal: 11111111111111111111111111110000//-8>>211111111111111111111111111111100//-2[/quote]Láthatjuk, hogy itt fontos szerepe van az MSB-nek. Ettõl függ ugyanis, hogy 0-k vagy 1-esek kerülnek a \"megüresedett\" bitek helyére. Fontos tehát megjegyezni, hogy a jobbra shiftelés megtartja a számok elõjelét, nem változtat azon!1.4.5.2 Balra toló operátor(<<):Ez az operátor a szám bitjeit balra tolja a megadott mértékkel. Egy bittel való eltolás, 2-vel való szorzásnak felel meg. Nézzünk egy példát: 00000000000000000000000000001000//8<<200000000000000000000000000100000//32[/quote]Itt megint könnyedén észrevehetõ mi is történt. Minden bitet fentebb \"toltunk\" 2-vel. Azok a bitek, amelyek ezáltal a 32. bit elé kerültek, kiesnek. Az így megüresedett bitek 0-kkal egészültek ki. Eddig nem túl bonyolult. Azonban figyeljünk oda, ha túl sokáig shiftelünk egy pozitív számot, akkor az utolsó érték, melyet fel tud venni a változónk (mielõtt még túlcsordulás miatt elérné a 0-t), az egy negatív szám!Nézzük mi történik negatív számok esetén: 11111111111111111111111111111000//-8<<211111111111111111111111111100000//-32[/quote]Láthatjuk hogy a balra shifteléskor a szám mindig 0-kkal egészítõdik ki, legyen az akár negatív, akár pozitív. Nem veszi figyelembe az MSB értékét, így ha túl sokat shiftelünk, akkor elõfordulhat, hogy a szám elveszti negatív értékét, és eléri a 0-t.1.4.6 Logikai biteltolás (logical shift, logikai shiftelés) operátorElõször is le kell tisztázni, hogy logikai shiftelésbõl csak egy létezik, jobbra logikai shiftelés(>>>). Sokban hasonlít a jobbra shiftelõ operátorra (>>), azonban van egy fontos különbség.Nézzük az elsõ példát: 00000000000000000000000000001000//8>>>200000000000000000000000000000010//2[/quote]Eddig nem igazán tapasztalhattunk semmi változást, minden pont ugyanúgy zajlott, mint a jobbra shiftelésnél(>>).Nézzük mi történik negatív szám esetén: 11111111111111111111111111111000//-8>>>200111111111111111111111111111110 //1073741822[/quote]Itt már láthatjuk a különbséget. Logikai shiftelés esetén az üres bitek helyére bekerülõ érték nem függ az MSB-tõl, hanem mindig 0. Emiatt soha nem kaphatunk eredményül negatív számot, logikai shiftelés esetén.1.5 Elsõ fejezet végeNos, ezennel átvettünk mindent, amit érdemes tudni a bitmûveletekrõl. Megmutattam az alapok, hogy hogyan épülnek fel a változók, valamint átvettük a fõ operátorokat. Eddig csak színtiszta elmélet volt. A következõ részben megmutatom, hogyan hasznosíthatjuk mindezt a gyakorlatban, vagyis mire is jó az a sok marhaság, amit eddig leírtam.2. Gyakorlati példák, érdekességek 2.1 Áttekintés, mikrõl is lesz még szóAz alábbiakban még pár dolgot szeretnék mutatni. Elõször is néhány \"függvénnyel\" fogok foglalkozni, majd mutatok még egy-két érdekességet. Konkrétan megmutatom, hogyan tudjuk egy jármû minden \"paneljének\" adatát kiíratni, részletesen(erre alapból nincs lehetõség, és a függvények is mind \"encode\" függvények, én viszont mutatok egy-egy decode \"verziót\" is.)Az alábbi 4 függvénnyel fogok foglalkozni: encode_tiresencode_panelsencode_doorsencode_lightsDe elõbb egy kis háttérinfó:A Pawnban a forrásban megadhatunk bináris számokat ugyanúgy, ahogyan Hex számokat is megadhatunk. Ehhez a \"0b\" prefixet kell használnunk. Tehát ha bináris számunk az 1011 és ezt akarjuk adni értékül egy változónak, azt így tehetjük meg: new var = 0b1011; 2.2 encode_tiresHáttér információ:A SA:MP-ban egy jármû abroncsainak adataid 4 bit tárolja. Az elsõ bit a jobb hátsó, a második bit a jobb elsõ, a harmadik bit a bal hátsó, a negyedik bit pedig a bal elsõ abroncsról tárol információt. Mivel 4 bitben eltárolhatóak az információk, így decimális formában 0-15 értékekkel is be lehet állítani a jármûvek abroncsait. 16-tól kezdve az értékek már ismétlõdni kezdenek, szóval a 16-nál az eredmény ugyanaz mint 0-nál, 17-nél ugyanaz mind 1-nél, és így tovább. Ez érthetõ, ha tudjuk hogyan épül fel a kettes számrendszer.(Ne felejtsük el, a biteket hátulról számozzuk!)Nos nézzük hogyan néz ki a kód: encode_tires(tire1, tire2, tire3, tire4){ return tire1 | tire2 << 1 | tire3 << 2 | tire4 << 3;} Láthatjuk, hogy bekérjük a 4 abroncs adatát(vagyis hogy ép[0], vagy kidurrant[1]), majd egy sima bitenkénti VAGY mûvelet és biteltolás kombinálásával visszatérünk az eredménnyel. A kódban a tire1 felel meg a jobb hátsó abroncsnak, a tire2 a jobb elsõ, a tire3 a bal hátsó, és a tire4 a bal elsõnek. Nézzünk egy konkrét példát. Tegyük fel, hogy én a hátsó 2 abroncsot ki akarom durrantani, még az elsõ kettõt épre akarom beállítani. Ez esetben így néz ki a dolog: encode_tire(1,0,1,0); Ha kiíratnánk konzolba a visszatérési értéket (%d-vel), 5-öt kapnánk. Miért is? Az 5 bináris alakja 0101. Hoppá, ha megnézzük ez pont fordítottja annak, amit mi megadtunk számjegyekként, csak már konkrét bináris számként. Nézzük hogyan is történhetett mindez, mit is csináltunk. A függvény a következõ mûveletet végzi: tire1 | tire2 << 1 | tire3 << 2 | tire4 << 3 Konyhanyelven úgy magyarázhatnánk el a dolgot, hogy ez a kód egymás mellé pakolgatja a megadott számokat, fordított sorrendben. Na de mi most nem a konyhanyelv miatt vagyunk ebben a leírásban, szóval nézzük a kódunkat. Lényegében ez futott le, a konkrét értékeinkkel nézve: 1 | 0 << 1 | 1 << 2 | 0 << 3 Bármily furcsa is, ez egy mûvelet, melyen szépen végig halad sorban a program. Elõször is írjuk át az egészet bináris alakba(az egyszerûség kedvéért most csak 4 bitet fogok kiírni): 0001 | 0000 << 1 | 0001 << 2 | 0000 << 3 Na erre se mondtuk volna rá a tutorial elsõ részének elolvasása elõtt, hogy ez egy mûveletsor. A rendszer elsõnek az eltolásokat végzi el (olyan ez mint a 2*3+2*4+2*5 mûveletben elõször a szorzásokat végezzük el, és csak utána adunk össze, ha nincs zárójel), és csak utána a bitenkénti VAGY mûveleteket, ezért mi is így fogunk tenni. Emlékezzünk, eltoláskor az \"üres\" bitek 0 értéket fognak felvenni. Végezzük el külön csak az eltolásokat: 0000 << 1 = 00000001 << 2 = 01000000 << 3 0000 Nos, ha ezeket elvégeztük, akkor nézzük, hogy is áll most a kódunk: 0001 | 0000 | 0100 | 0000 Négy azonos mûvelet van hátra, így hát a program innen más szépen sorban fog haladni. Tegyünk mi is így. Elsõ tehát a 0001 | 0000 mûvelet elvégzése. Emlékeztetõül, a bitenkénti VAGY mûvelet akkor ad 0-t eredményül, ha mind a kettõ érték 0, minden más esetben 1-et. Végezzük el külön az elsõ bitenkénti VAGY mûveletet: 0001 | 0000 = 0001 Nem volt bonyolult, nézzük tehát hogy áll akkor most a teljes mûveletünk: 0001 | 0100 | 0000 Haladunk szépen tovább, elvégezzük a 0001 | 0100 mûveletet. 0001 | 0100 = 0101 Nincs mit magyarázni rajta, egyszerû bitenkénti VAGY mûvelet volt ez is. Már csak ez van hátra: 0101 | 0000 Nos, ez sem lesz egy bonyolult mûvelet: 0101 | 0000 = 0101 Ezzel meg is kaptuk a végeredményt, ami nem más, mint a 0101 (tízes számrendszerben 5). Mivel azonban eléggé kusza lett így a számításunk, nézzük egyben hogyan is néz ki az egész, ha nem bontom ennyire részekre: 0100 | 0000 << 1 | 0001 << 2 | 0000 << 3v0001 | 0000 | 0100 | 0000v0001 | 0100 | 0000v0101 | 0000v0101 Nos, ezennel láthattuk a gyakorlatban is, hogyan mûködik a függvény. Kijelenthetjük tehát, hogy a függvény a számokat a sorrendjüknek megfelelõ számú bitbe helyezi. Mivel azonban a biteket hátulról számozzuk, így az 1,0,1,0 bemenetek végeredménye 0101 lesz. Így már nem is olyan bonyolult Na, ha már ilyen sokáig eljutottunk, akkor felhasználhatjuk ezt a kódot arra, hogy az alapjait figyelembe véve elkészítsük a decode változatot. Mivel 4 értékkel térünk vissza, ezért paraméterként kell majd 4 változó, melyeknek módosíthatjuk az értékét, illetve 1 paraméter pedig a dekódolandó szám. A függvényünk \"címsora\" tehát így néz ki: decode_tires(tires, &tire1, &tire2, &tire3, &tire4) Hogy példánk ne csak elmélet legyen, a tires változó legyen az elõbbi 0101 értékkel egyenlõ. Na akkor menjünk tovább. Bemeneti értékünk ugye a \"tires\" változóban lesz eltárolva, így ezzel fogjuk a mûveleteket végezni. Kezdjünk el gondolkozni. Hogyan is tudnánk ellenõrizni egy adatot. Legegyszerûbb ha a bitenkénti ÉS mûveletet alkalmazzuk. Miért is? Nos, a bitenkénti ÉS mûvelet ugye akkor ad eredményül 1-et, ha mind a 2 érték egy. Tehát ha min egy számot összehasonlítunk a 1-el(binárisan ugye 00000000000000000000000000000001, akkor az eredményünk lényegében arról fog tájékoztatni, hogy az elsõ bit 0, vagy 1. Nem is olyan bonyolult ez. Nézzük akkor az elsõ bitet. Arra emlékezhetünk még, hogy az elsõ bit a tire1, vagyis jobb hátsó abroncs adatait tárolja. Tehát: tire1 = tires & 1; Vagy konkrétan a példánk esetében: tire1 = 0101 & 0001 Mivel a bitenkénti ÉS mûvelet akkor ad 1-et eredményül, ha mind a kettõ bit 1, akkor már láthatjuk is, hogy miért elegendõ az elsõ bitet szemügyre venni. Mivel mind a 2 helyen az elsõ bit 1, ezért a tire1 változónk értéke is 1(0001) lesz. Egyszerû és nagyszerû, máris megkaptuk a jobb hátsó abroncs állapotát.Gondolkozzunk hát tovább. Nekünk most arra van szükségünk, hogy a 2. bit értékét megkapjuk. Hogyan kaphatjuk ezt meg? Legegyszerûbb megoldás, ha a tires változót jobbra shifteljük 1-el, majd ismételten bitenkénti ÉS mûveletet végzünk. Nézzük tehát, hogyan is nézne ez ki: tire2= tires >> 1 & 1; Vagy konkrétan a mi példánkban: tire2 = 0101 >> 1 & 0001vtire2 = 0010 & 0001 Ismételten bitenkénti ÉS mûvelet, és az eredményünk 0, mivel csak az egyik bit 1, a másik 0, így a bitenkénti ÉS mûvelet eredménye is 0. Ezen az elven megcsináljuk a maradék 2 abronccsal is: tire3= tires >> 2 & 1;tire4= tires >> 3 & 1; Vagy a mi esetünkben konkrét példánkkal: tire3 = 0101 >> 2 & 0001 // azaz: --> 0001 & 0001 = 1tire4 = 0101 >> 3 & 0001 // azaz: --> 0000 & 0001 = 0 A teljes kódunk tehát: decode_tires(tires, &tire1, &tire2, &tire3, &tire4){ tire1 = tires & 1; tire2 = tires >> 1 & 1; tire3 = tires >> 2 & 1; tire4 = tires >> 3 & 1;} A konkrét példánkban a 4 változónk értéke a függvény lefutása után: tire1: 1tire2: 0tire3: 1tire4: 0 Ha összehasonlítjuk, ezek pont azok az értékek, mint amiket az encode függvényben megadtunk, tehát úgy tûnik jól dolgoztunk 2.3 encode_lightsEz a rész lesz a legrövidebb. Az egész lámpás rész pontosan ugyanarra a sémára épül mint az abroncsok. Az adott bitek ugyanazon a \"helyen\" lévõ lámpa adatait szolgáltatják, ugyanúgy 0 az ép, és 1 a sérült lámpák állapot azonosítója, és ugyanúgy 1-1 bit tárolja az értékeket.Maga a kód: encode_lights(light1, light2, light3, light4){ return light1 | light2 << 1 | light3 << 2 | light4 << 3;} Láthatjuk hogy halál pontosan ugyanaz mint az encode_tires, csak más változónevekkel. Így hát a decode függvény is ugyan az lesz, csak a változóneveket kell módosítanunk: decode_lights(lights, &light1, &light2, &light3, &light4){ light1 = lights & 1; light2 = lights >> 1 & 1; light3 = lights >> 2 & 1; light4 = lights >> 3 & 1;} Ha megnézzük látjuk, hogy teljesen ugyan az a séma, ugyan az a felépítés, minden megegyezik, ezért nem is írtam le újból ugyanazt, amit egyszer már kifejtettem.2.4 encode_panelsHáttér információ:A SA:MP 7 panelrõl tárol adatokat. Egy panel adatait 4 bit írja le. Nos, ennél több infóra nekünk most nincs szükségünk, az elõzõ kód alapján ezen kód is megérthetõ, így az encode verzióhoz nem is nagyon szeretnék magyarázatot fûzni. A különbség az encode_tires-hez képest, hogy itt 4-essével shiftelünk, mivel 1 panel adatait 4 bit tárolja.Maga a kód: encode_panels(flp, frp, rlp, rrp, windshield, front_bumper, rear_bumper){ return flp | frp << 4 | rlp << 8 | rrp << 12 | windshield << 16 | front_bumper << 20 | rear_bumper << 24;} Nézzünk egy decode verziót, ehhez a kódhoz. Alapjaiban ugyanazon a vonalon kell elindulnunk, mint a decode_tires esetében. Itt most 7 visszatérési értékünk lesz, és 1 bemeneti érték. Tehát így néz ki a függvény header: decode_panels(panels, &flp, &frp, &rlp, &rrp, &windshield, &front_bumper, &rear_bumper) A folytatásban megint csak az elõbbi verziót kell alapul vennünk, azonban itt lesz egy kis különbség. Egyértelmû, hogy mivel 1 panel adatait 4 bit tárolja, így a 4 bitet nekünk egyben kell lekérni, és egyben kell vele visszatérnünk. Megint csak a bitenkénti ÉS mûveletet fogjuk alkalmazni, azonban mivel 4 bitrõl van szó, így egy olyan számmal kell összehasonlítanunk, mely bináris alakban 1111. Ez a szám nem más, mint a 15. Hogy miért ez kell?Ha egy bitsorozat tartalmát ki szeretnénk másolni egy másik változóba, akkor a bitenkénti ÉS mûveletre vna szükségünk. A bitenkénti ÉS mûvelet akkor ad 1-et eredményül, ha mindkét bit 1, ha valamelyik 0, akkor a visszatérés már 0 lesz. Ergó tehát, ha egy csupa 1-esekbõl álló bitsorozattal vetjük össze az eredeti bitsorozatunkat, akkor lényegében lemásoljuk a sorozatot, egy másik változóba, hisz eredményül mindig az eredeti sozatot fogjuk visszakapni. Íme néhány példa: 0101 & 1111 = 01011111 & 1111 = 11110000 & 1111 = 00001011 & 1111 = 1011 Láthatjuk tehát, hogy ha csupa 1-est tartalmazó bitsorozattal hasonlítjuk össze az eredeti számunkat, akkor lényegében egy másolatot készítünk. Mivel nekünk erre a 4 bitre van szükségünk a 32-bõl, így pontosan ezt kell tennünk, ezen 4 bitet lemásolni egy másik változóba, hogy késõbb ezt majd egy másik függvénnyel még jobban darabokra szedhessük.Íme tehát akkor a kódunk elsõ sora: flp = panels & 15; Mivel 4-esével vizsgáljuk a biteket, így lényegében annyit kell még változtatnunk az elõzõ kódon(decode_tires), hogy az 1-esével növekvõ shiftelések helyett 4-esével shifteljünk. Azaz a 2. panel adatainak lekérdezése: frp = panels >> 4 & 15; Innen pedig már csak mindig további 4 shiftelést el kell végeznünk, és szépen végig haladhatunk a panels változó bitjein, és megkaphatjuk minden panel adatait külön-külön. rlp = panels >> 8 & 15;rrp = panels >> 12 & 15;windshield = panels >> 16 & 15;front_bumper= panels >> 20 & 15;rear_bumper = panels >> 24 & 15; Ezennel minden panel adatait megkaptuk. Ezután már akár a decode_tires függvényt felhasználva szét lehet szedni az adatokat darabokra, de akár egy saját névvel, és más változó nevekkel ellátott másik függvényt is létre lehet hozni a decode_tires mintájára.Tehát az egész kód egyben: decode_panels(panels, &flp, &frp, &rlp, &rrp, &windshield, &front_bumper, &rear_bumper){ flp = panels & 15; frp = panels >> 4 & 15; rlp = panels >> 8 & 15; rrp = panels >> 12 & 15; windshield = panels >> 16 & 15; front_bumper= panels >> 20 & 15; rear_bumper = panels >> 24 & 15;} Igazából a függvény ugyanarra a sémára épül mint a decode_tires, csak annyi a különbség, hogy 4 bitenként kapjuk az adatokat, nem pedig egyesével, mivel itt minden panelnek 4 adatát tudjuk majd még lekérdezni. Nem tudom lehet-e még ehhez a részhez hozzáfûzni valamit, szóval szerintem ugorjunk. 2.5 encode_doorsHáttér információ:A SA:MP képes minden jármû 4 \"ajtajának\" állapotát tárolni. Ezek az \"ajtók\"(Doors-t hogy fordíthatnám jobban?) konkrétan a motorháztetõ, csomagtartó, vezetõoldali ajtó, és anyósülés oldali ajtó. A 2 hátsó ajtó állapotát jelenleg még nincs lehetõségünk lekérdezni.Amikor lekérdezzük egy jármû \"ajtajának\" adatait, azt a SA:MP 4 bájtként(32 bit) adja vissza. Ebbõl a 4 bájtból 1 bájt(8 bit) jut minden egyes objektumra. Vagyis minden egyes objektum adatait 8 bit tárolja. Vagyis tárolhatná...A SA:MP csak az elsõ 3 bitet használja, a további bitek üresek lesznek, nincs funkciójuk, talán majd késõbb lesz. Az elsõ bit azt tárolja, hogy az objektum zárt[0], vagy nyitott[1](nem az jelenti, hogy bezárt, hanem becsukott/kinyitott) állapotban van-e. A második bit azt adja meg, hogy az objektum ép[0] vagy sérült[1]. A 3. bit arról tájékoztat, hogy az adott objektum rajta van-e még a jármûvön[0] vagy már leesett[1]. A többi bit mint mondtam üres. Ez majd a decode függvénynél lesz fontos.Nézzük tehát a konkrét kódot: encode_doors(bonnet, boot, driver_door, passenger_door, behind_driver_door, behind_passenger_door){ #pragma unused behind_driver_door #pragma unused behind_passenger_door return bonnet | boot << 8 | driver_door << 16 | passenger_door << 24;} A 2 hátsó ajtóval nem foglalkozunk, mivel azok jelenleg még nem támogatottak ugyebár, de a kód készítõje gondolt a jövõre is, vagy fogalmam sincs miért adta meg ezt a két paramétert is, hisz ígyis-úgyis módosítani kell a kódon... Ha behoznak még 2 ajtót, akkor az 1 bájtos tárolással már 6 bájt lenne, de 1 változó maximum 4 bájt lehet, tehát a kódot úgyis módosítani kell, ha meg arra gondoltak, hogy legalább a paramétereket nem kell majd szegény scriptereknek minden sorban átírni, az megint hülyeség, hisz ki adna értéket olyan paraméternek, amely nincs használva?...Nos mindegy, nem a kritizálásért vagyunk most itt. Ha megnézzük, a kód 8-as shiftelést használ, mivel egy objektum adatait 8 bit tárolja. A mûködési elv ugyanaz, mint az elõzõ encode függvényeknél, így erre nem kívánok megint kitérni.Nézzük azonban hogyan írnánk ehhez egy decode függvényt. Mikor megkérdeztem errõl ismerõsömet, õ azt mondta, ugyanúgy mint az elõzõ, decode_panels függvénynél, csak 8-as shiftelést használunk, és 15 helyett 255-el végezzük a bitenkénti ÉS mûveletet. 255-el? minek?No erre is ki fogok térni késõbb, de haladjunk a szokásos módon. 1 bemeneti paraméterünk van, és 4 kimeneti, vagyis a header rész a következõ: decode_doors(doors, &bonnet, &boot, &driver_door, &passenger_door) Ezzel meg is vagyunk, itt nem látok különösebb magyarázni valót. Én most nem fogok törõdni a 2 nem létezõ ajtóval, ha egy jövõbeni verzióban netán azokat is behoznák mint lekérdezhetõ objektum, akkor ígyis-úgyis módosítani kell majd a kódot, addig meg úgysincs rá szükségünk.Nos, mint az eddigi decode függvényeknél, elsõ feladatunk egy shiftelés mentes bitenkénti ÉS mûvelet elvégzése. Az egyik megoldás a következõ: bonnet = doors & 255; Lehet így is. Azonban véleményem szerint felesleges itt egy ekkora számmal \"szenvednünk\". Miért használjunk ekkora számot, és ezáltal bonyolítsuk feleslegesen a kódunkat, ha nekünk a 8 bitbõl csak az elsõ 3 lesz ami adatot tárol? Bõven elegendõ egy olyan szám, amely bináris alakja 111. Ez nem más, mint a 7. Tehát a következõ megoldás ugyanúgy helyes: bonnet = doors & 7; A két kód pontosan ugyanazt az eredményt fogja visszaadni, semmi különbség a kettõ között. Csupán elsõ esetében nagy számmal dolgozunk, még második verzió esetén csak akkorával, amekkora szükséges.A kód többi részében csak 8-as shifteléssel végigcammogunk a doors változó többi bitjén is: boot = doors >> 8 & 7;driver_door = doors >> 16 & 7;passenger_door = doors >> 24 & 7; A teljes kód egyben: decode_doors(doors, &bonnet, &boot, &driver_door, &passenger_door){ bonnet = doors & 7; boot = doors >> 8 & 7; driver_door = doors >> 16 & 7; passenger_door = doors >> 24 & 7;} A kód pont ugyanazon az elven épül fel, mint az összes többi decode függvényünk, csak épp a benne használatos értékek az \"ajtó\" objectumok adatstruktúrájához vannak igazítva.2.6 Egyéb ötletek gyakorlati alkalmazásra, hasznosításraJoggal felmerülhet a kérdés, hogy ennyi? Emiatt rágtuk végig magunkat ezen néhány tízezer karakteren? Nem. Nem ennyi. A SA:MP-ban rengeteg felhasználási módja lehet még a bitmûveleteknek. Mutatok is néhány dolgot, mire jók még.2.6.1 RGBA->HEX; HEX->RGBA konvertálásokNos, bármily \"hihetetlen\", erre is használható. Miért is? Nos, 16-os számrendszerben 0-9, valamint A-F \"szánjegyek\" léteznek. A hexadecimális számjegy decimális értékben 0-15 lehet. Ha megnézzük bináris értékeket, 0-15 az 4 bittel leírható, hiszen a 15 az bináris alakban 1111. De ha józan paraszti ésszel belegondolunk is. 16 az 24. Így tehát 1 hexadecimális érték, az 4 bináris értékkel írható le. Ebbõl már sejthetõ, hogy itt is shiftelésre lesz szükségünk. Naná Mivel 4 bit ír le egy hexadecimális számjegyet, így 4-esével kell majd shiftelnünk.Mi is az az RGBA?RGBA: A színek olyan meghatározási módja, melyben a 3 alapszín([r]ed, [g]reen, lue) mellett egy 4. érték, az úgynevezett [a]lpha is szerepel, amely az áttetszõséget adja meg. A legtöbb program(pl. paint, PS), és pl. a css(külön függvénnyel!) is elfogadja ezen értékeket decimális számokként(0-255) is, a SA:MP-ban azonban hexadecimális alakban kell megadnunk. Bármily furcsa a következõ 4 sor pont ugyan azt jelenti:\"Normál\" RGB alak(pl. paint, photoshop): Piros: 0, Zöld: 255, Kék: 255CSS alak(függvénnyel): rgba(0, 255, 255, 1);Pawn alak(1): 0x00FFFFFFPawn alak(2): 16777215 Ó igen, a Pawn-ban decimális számokként is megadhatjuk a színeket, hisz a program mindent bináris számrendszerbe fog átszámolni. De akár bináris alakban is megadhattuk volna:Pawn alak(3): 0b111111111111111111111111 Az, hogy mi a Pawn-nak hexadecimális alakban szoktuk megadni, annak az egyetlen oka, hogy így a leginkább \"átlátható\", és ez egy amúgy is elfogadott módja (mármint a hexadecimális számmal való megadás) a színek leírásának a számítástechnikában.Nos, mivel 1 szín 0 és 255 közötti értéket tartalmaz a piros, zöld és kék színekbõl, ez hexadecimálisan 2 számjeggyel leírható: 00-FF. Az FF értéke 255, aki nem hiszi, számoljon utána (162-1)2 hexadecimális számjegy az pedig 8 bináris számjeggyel leírható(00000000-11111111). Így már láthatjuk, hogy 8-as shiftelésre lesz szükségünk. Másra nincs is szükségünk, az RGBA->HEX konverter elkészítéséhez.stock RGBAToHex( r, g, b, a ){return a | b << 8 | g << 16 | r << 24;} Az, hogy miért fordított sorrendben kellett megadnunk az értékeket, az megint csak azzal magyarázható, hogy a biteket hétköznapi értelemben véve visszafelé számozzuk.Egy ellentét, azaz HexToRGBA függvény megírása sem túl bonyolult, ugyan ezt kell csinálnunk, csak visszafelé.stock HexToRGBA( colour, &r, &g, &b, &a ){a = colour & 0xFF;b = (colour >> 8 ) & 0xFF;g = (colour >> 16 ) & 0xFF;r = (colour >> 24 ) & 0xFF;} A 0xFF-et természetesen decimális értékkel is megadhatjuk(255).No akkor lehet ugye RGBA-t ARGB-be is konvertálni, az így néz ki:RGBAToARGBHEX(r,g,b,a){return (r & 0xff) << 24 | (g & 0xff) << 16 | (b & 0xff) << 8 | a & 0xff;} Ez máris egy újabb felhasználási módja a bitmûveleteknek. De, mutatok még 2.6.2 Boolen változók helyett IntegerAz al-al-alcímet elolvasva joggal felmerülhet a kérdés, hogy ez meg hogy jön ide. Nos ez úgy jön ide, hogy most egy kis memóriatakarékosságról fogok itt regélni. Adhattam volna azt is címnek, hogy miért ne használjunk boolean változókat, ha legalább 5 igaz/hamis értéket el akarunk tárolni.Nos akkor egy kis memórialecke. Azt már tisztáztuk, hogy az integer változók 32 bitesek, vagyis 4 bájtot foglalnak le a memóriában maguknak. A boolean típusú változókról azonban még nem volt szó. Mivel 1 értéket tárolnak csak, az is csak igaz vagy hamis lehet, így joggal gondolhatnánk, hogy 1 bit a mérete. Azonban nem...1 boolean változó 8 bitet, azaz 1 bájtot foglal le magának a memóriában. Hogy ez miért így van, az a számítógépek memóriahasználatával magyarázható. Manapság nincs mód arra, hogy a memóriában egyetlen bitet módosítsunk, csak 8-as \"csomagokban\", vagyis bájtonként tudjuk ezt megtenni. Hogy ez miért van így, azt most nem fogom itt fejtegetni Nos, ha megnézzük tehát, a boolean változó egy nagyon-nagyon pazarló megoldás. Hiszen 1 darab boolean változó 1 bájtot foglal, 4 darab boolean változó esetén ez már 4 bájt, ami megegyezik 1 integer változó méretével. Azonban, még a 4 boolean változóban 4 igaz/hamis érték kerülhet tárolásra, addig egy integerben 32 igaz/hamis értéket is eltárolhatunk.Bizony, 32 igaz/hamis érték. Hiszen 1 integer változó 32 bitbõl áll, ha minden bitet hasznosítunk, akkor a bitmûveletek felhasználásával 32 igaz/hamis értéket is eltárolhatunk. Összehasonlítva:32 darab boolean változó mérete 32 bájt.1 integer változó mérete: 4 bájt. Vagyis 32 boolean változó esetén nyolcad annyi memóriánkba kerül, ha egy darab integerben tároljuk el az értékeket. Persze 32 booleanra ritkán van szükség, de egy RP modban simán elõfordulhat. Gondoljunk csak bele... Van 32 boolean-tömbös változónk, ez a maximum slotszámot(1000) figyelembe véve: 32*1000 byte azaz 32000 byte ami ~31 kilobyte. Ha ezt egyetlen integer-tömbbel oldanánk meg: 4*1000 byte, azaz 4000 byte, ami ~3,9 kilobyte. Azért így nézve már nem annyira \"elhanyagolandó\" az a különbség szerintem =)Akár írhatnánk erre külön egy függvényt, mely az adott bit értékével tér vissza. Nem is bonyolult. Így már azok számára is használhatóak a bitmûveletek, akik nem értenek hozzá, és rengeteg memóriát takaríthatnak meg.Íme egy függvény az adatok kiolvasására:stock GetData(integer, bit){ return integer >> bit & 1; //vagy az 1 helyett akár 0x01 alakban is megadhatjuk, nincs jelentõsége} És a párja, adat írásra:stock SetData(&integer, bit, value){ integer |= value << bit; return 1;} Persze ezt még lehetne csinosítani, hogy pl. ne 0-tól hanem 1-tõl számoljuk a biteket, vagy hogy 31(vagy 32 ha 1-tõl számolunk) felett 0 legyen a return értéke, esetleg a value-re is rakhatnánk ellenõrzést, hogy 0-1-e az értéke, vagy más, de ilyenekkel most nem törõdtem.2.6.3 Több érték, egy integerbeNa ez az a rész, ami véleményem szerint már csak a nagyon megrögzött memóriafelszabadítók szerint nem lesz felesleges Amennyiben nincs szükségünk túl nagy számokra(pl. csak 0-15, vagy akár csak 0-7, de akár még a 0-255 is megfelel), akkor akár egy integerbe is tehetjük õket. Hogyan? Egyszerû. Csoportosítjuk a biteket. Tegyük fel, hogy maradván egy RP szervernél, el akarjuk tárolni ûbergigantikusan memóriakímélõ megoldással a játékos házának számát(ez legyen mondjuk 0-100 közötti szám), a játékos frakcióját(ez legyen 0-15 között), a játékos rangját(0-7 között), hogy a telefonja be van-e kapcsolva(0-1), a játékos szolgálati jármûvének id-jét(legyen 0-255 között), és az alapmunkáját(0-15 között). Ez is megoldható, egyetlen integerben, és ismételten 1000 slottal számolva máris megspóroltunk 4*4*1000+1*1000 byte-t(5 integer, és 1 boolean helyett csupán 1 integert használunk), ami ~16,6 kilobyte. Azért nem mindegy az, hogy 20,5 kb-ot vagy 4 kb-ot használunk szerintem... Ezt a következõ módszerrel valósíthatnánk meg:#define DATA_HOUSE 0#define DATA_FACTION 1#define DATA_RANK 2#define DATA_PHONESTATE 3#define DATA_DUTYCAR 4#define DATA_JOB 5stock GetData(playerinfo, data){ switch(data) { case DATA_HOUSE: return playerinfo & 127;//7 bit case DATA_FACTION: return playerinfo >> 7 & 15;//4 bit case DATA_RANK: return playerinfo >> 11 & 7;//3 bit case DATA_PHONESTATE: return playerinfo >> 14 & 1;//1 bit case DATA_DUTYCAR: return playerinfo >> 15 & 255;//8 bit case DATA_JOB: return playerinfo >> 23 & 15;//4 bit}return -1;}stock SetData(&playerinfo, data, value){ switch(data) { case DATA_HOUSE: { playerinfo = playerinfo & ~127 | value; }//7 bit (0-127 közötti maximálisan) case DATA_FACTION: { playerinfo = playerinfo & ~(15 << 7) | value << 7; }//4 bit (0-15 között) case DATA_RANK: { playerinfo = playerinfo & ~(7 << 11) | value << 11; }//3 bit (0-7 között) case DATA_PHONESTATE: { playerinfo = playerinfo & ~(1 << 14) | value << 14; }//1 bit (0-1) case DATA_DUTYCAR: { playerinfo = playerinfo & ~(255 << 15) | value << 15; }//8 bit (0-255 között) case DATA_JOB: { playerinfo = playerinfo & ~(15 << 23) | value << 23; }//4 bit (0-15 között) } return -1;}stock SetDatas(&playerinfo, house, faction, rank, phonestate, dutycar, job){playerinfo |= house | (faction << 7) | (rank << 11) | (phonestate << 14) | (dutycar << 15) | (job << 23);} Ez már azért elég morbid, ezt inkább csak érdekességképp mutattam, de éppenséggel ez is használható lenne, csak ehhez már kell egy kis õrültség 2.7 Második fejezet végeNos, láthattuk hogyan is mûködik mindazon sok \"marhaság\", amit az elsõ fejezetben elméletként átvettünk. Láthattuk hogyan mûködnek a jármûvek paneljének adataival babráló encode függvények, valamint megmutattam, hogyan írhatunk hozzájuk decode verziókat. Láthattunk még példát egyéb gyakorlati hasznosításra, kicsit megkoccoltuk a hexadecimális számok világát is, majd szóba került egy kis memóriatakarékosság. Végül mutattam egy összetettebb, elvetemült példát is, a bitmûveletek hasznosítására.3. Összegzés, végszó Úgy gondolom, hogy elég részletesen körbe jártuk a témát. Megnéztük a bitmûveletek elméleti, és gyakorlati oldalát is, láthattunk gyakorlati példákat. Nem kell elsõre megérteni ezt az egészet, sõt... Elegendõ ha egyszer elolvassuk, emésztjük kicsit, majd késõbb megint elõvesszük, akkor netán már kicsit többet is megértünk belõle. Nem egy kihagyhatatlan része a Pawno-nak, de azért van annak haszna, ha az ember jártas ebben a témában is, bár sokak szerint nem éri meg a sok fáradtságot, és idõt, mely a megértéséhez, begyakorlásához, használatának elsajátításához szükséges. Próbáltam mindent részletesen kifejteni. A bonyolultabb dolgokhoz sokat magyarázni, az egyszerûbbek esetén csak visszautalni az alapokhoz. Sok dolgot talán már túl is magyaráztam. Ezt a leírást nem 1 nap alatt készítettem el, kb. 1 hónapja írtam meg az elméleti oldalát, most az elmúlt pár napban pedig a gyakorlatot. Esténként 1-2 órát szántam rá csupán, és mivel ilyenkor már nem vagyok épp a toppon, elképzelhetõ, hogy néhány helyen helyesírási, vagy elvi hibát vétettem. Amennyiben valaki ilyet találna, kérem szóljon, és korrigálni fogom. Nos, azt hiszem nincs is más hátra, mint hogy sok szerencsét kívánjak a bitmûveletek elsajátításához, és megosszam a forrásokat. Remélem néhányan hasznosnak találják, eme igen hosszúra sikerült leírást.4. Források: Internet:WikipédiaSA:MP WikiSA:MP hivatalos fórumKönyv, e-book:Pohl László - A programozás alapjai(BME - Elektronikus Eszközök Tanszéke, Budapest, 2010)Siroki László - C programozás - Bitmûveletek jegyzeteˆ Anthony, 2012 - Minden jog fenntartva.A leírás egészben, vagy részleteiben szabadon terjeszthetõ, ameddig egyértelmûen fel van tüntetve az eredeti készítõ, valamint hogy honnan is származik. « Utoljára szerkesztve: 2012. augusztus 01. - 10:53:54 írta Anthony » Naplózva
00000000000000000000000000001011&0000000000000000000000000000011100000000000000000000000000000011[/quote]Eredményül tehát 00000000000000000000000000000011-et kaptunk, ami a kettes számrendszerben a 3-asnak felel meg. A következõ 2 sor tehát ugyanazt jelenti: Bin:00000000000000000000000000001011&00000000000000000000000000000111=00000000000000000000000000000011Dec:11&7=3[/quote]Még néhány példa, de ezeket már nem részletezem: 00000000000000000000000000000011 & 00000000000000000000000000000101 = 0000000000000000000000000000000100000000000000000000000001110110 & 00000000000000000000000001011001 = 00000000000000000000000001010000[/quote]1.4.2 Bitenkénti (bitek közötti) VAGY mûveletA bitenkénti VAGY mûvelet (OR, |) egy logikai operátor. Fontos, hogy nem összekeverendõ a \"||\" logikai operátorral! Hasonlít rá, de ez a mûveleteket bitenként végzi. Ez az operátor akkor ad eredményül 1-et, ha legalább az egyik bit 1. Ha egyik bit sem 1, akkor 0-t ad eredményül, ha mindkét bit 1, akkor is 1 az eredmény. Igazság táblázata: A B A|B000101011111[/quote]Példának nézzük meg, mi történik, ha a bitenkénti VAGY operátort alkalmazzuk, szintén a 11 és a 7 esetén. A 11 bináris számrendszerben mint már írtam 00000000000000000000000000001011-nek felel meg, a 7 pedig 00000000000000000000000000000111-nek. Tehát a 11 | 7, az ugyaz mint ha ezt írnám: 00000000000000000000000000001011 | 00000000000000000000000000000111. Az átláthatóság kedvéért ismét egymás alá fogom írni a 2 számot, hogy áttekinthetõbb legyen, és az eredményt pedig a 3. sorba: 00000000000000000000000000001011|0000000000000000000000000000011100000000000000000000000000001111[/quote]Eredményül tehát 00000000000000000000000000001111-et kaptunk, ami a kettes számrendszerben a 15-nekk felel meg. A következõ 2 sor tehát ugyanazt jelenti: Bin:00000000000000000000000000001011|00000000000000000000000000000111=00000000000000000000000000001111Dec:11|7=15[/quote]Megint néhány példa: 00000000000000000000000000000011 | 00000000000000000000000000000101 = 0000000000000000000000000000011100000000000000000000000001110110 | 00000000000000000000000001011001 = 00000000000000000000000001111111[/quote]1.4.3 Bitenkénti (bitek közötti) KIZÁRÓ VAGY mûveletA bitenkénti KIZÁRÓ VAGY mûvelet (XOR, ^) egy logikai operátor. Ez az operátor nagyon hasonlít a VAGY operátorra, az a fõ különbség a kettõ között, hogy amennyiben mindkét bit 1-es, akkor a KIZÁRÓ VAGY operátor 0-t ad eredményül, a többi esetben ugyanúgy viselkedik mint a VAGY operátor. Igazság táblázata: A B A^B000101011110[/quote]Példának nézzük meg, mi történik, ha a bitenkénti KIZÁRÓ VAGY operátort alkalmazzuk, szintén a 11 és a 7 esetén. A 11 bináris számrendszerben mint már írtam 00000000000000000000000000001011-nek felel meg, a 7 pedig 00000000000000000000000000000111-nek. Tehát a 11 ^ 7, az ugyaz mint ha ezt írnám: 00000000000000000000000000001011 ^ 00000000000000000000000000000111. Az átláthatóság kedvéért ismét egymás alá fogom írni a 2 számot, hogy áttekinthetõbb legyen, és az eredményt pedig a 3. sorba: 00000000000000000000000000001011^0000000000000000000000000000011100000000000000000000000000001100[/quote]Eredményül most 00000000000000000000000000001100-t kaptunk, ami a kettes számrendszerben a 12-nek felel meg. Érdekes, hogy teljesen más eredményt kapunk, pedig lényegében ugyanazt az eredményt adja az operátor, - kis különbséggel ugyebár - mint a VAGY operátor.A következõ 2 sor tehát ugyanazt jelenti: Bin:00000000000000000000000000001011^00000000000000000000000000000111=00000000000000000000000000001100Dec:11^7=12[/quote]Megint néhány példa: 00000000000000000000000000000011 ^ 00000000000000000000000000000101 = 0000000000000000000000000000011000000000000000000000000001110110 ^ 00000000000000000000000001011001 = 00000000000000000000000000101111[/quote]Ezzel kapcsolatban egy érdekességet mutatnék meg. Vegyünk 2 számot. A látványosság kedvéért én példának a 118-at és a 89-et veszem. 1. szám: 00000000000000000000000001110110 = 1182. szám: 00000000000000000000000001011001 = 89Mûvelet elvégzése: 00000000000000000000000001110110 ^ 00000000000000000000000001011001 = 00000000000000000000000000101111 = 471. szám az eredmény, 2. szám az eredeti 2. szám: 00000000000000000000000001011001 = 89Eredmény: 00000000000000000000000000101111 ^ 00000000000000000000000001011001 = 00000000000000000000000001110110 = 118[/quote]Vagyis visszakaptuk a kiindulási állapotot 1.4.4 Bitenkénti (bitek közötti) NEGÁLÁSA bitenkénti NEGÁLÁS (NOT, ~) az utolsó logikai operátor, melyet meg fogok mutatni. Ez az operátor eléggé eltér az eddigiektõl, leginkább abban, hogy ezt a mûveletet nem 2 bitre alkalmazzuk, hanem csak 1-re. Szokás komplementer-operátornak is hívni(konyhanyelven). Lényegében az 1-esekbõl 0-t, a 0-kból 1-est csinál. Igazság táblázata: A ~A0110[/quote]Példának nézzük meg, mi történik, ha a bitenkénti NEGÁLÁST alkalmazzuk a 7-es számra. A 7 bináris számrendszerben mint már írtam 00000000000000000000000000000111-nek felel meg. Tehát a ~7, az ugyaz mint ha ezt írnám: ~00000000000000000000000000000111. Nézzük tehát, mi történik: ~0000000000000000000000000000011111111111111111111111111111111000[/quote]Eredményül most 11111111111111111111111111111000-t kaptunk, ami a kettes számrendszerben a -8-nak felel meg. A következõ 2 sor tehát ugyanazt jelenti: Bin:~00000000000000000000000000000111=11111111111111111111111111111000Dec:~7=-8[/quote]Megint néhány példa: ~00000000000000000000000000000101 = 11111111111111111111111111111011~00000000000000000000000001110110= 11111111111111111111111110001010[/quote]Ehhez kapcsolódóan lenne itt egy fontos megjegyzendõ dolog. Aki nem érti, hogy miért -8 a megoldás, az nem olvasta el elég rendesen a \"Részletesebben a bitekrõl a Pawn-ban\" részt.1.4.5 Biteltolás (arithmetic shift, shiftelés) operátor(ok)A shift operátorok(<<, >>) a bitek tologatására használhatóak. A << balra tol, a >> jobbra. Bal oldalt található a szám, amit tologatni kívánunk, jobb oldalt pedig az, hogy mennyivel szeretnénk eltolni.1.4.5.1 Jobbra toló operátor(>>):Ez az operátor a szám bitjeit jobbra tolja a megadott mértékkel. Egy bittel való eltolás, 2-vel való osztásnak felel meg. Nézzünk egy példát: 00000000000000000000000000001000//8>>200000000000000000000000000000010//2[/quote]Láthatjuk, hogy semmi más nem történt, mint minden bitet 2-vel hátrébb mozgattunk. Az így \"megüresedett\" bitek helyére pedig 0-k kerültek. Lényegében az történt, hogy az utolsó 2 bit kiesett, az így megmaradt biteket 2-vel hátrébb csúsztattuk, majd az elsõ 2, így \"üres\" bit 0 értéket kapott.Nézzünk egy példát negatív számmal: 11111111111111111111111111110000//-8>>211111111111111111111111111111100//-2[/quote]Láthatjuk, hogy itt fontos szerepe van az MSB-nek. Ettõl függ ugyanis, hogy 0-k vagy 1-esek kerülnek a \"megüresedett\" bitek helyére. Fontos tehát megjegyezni, hogy a jobbra shiftelés megtartja a számok elõjelét, nem változtat azon!1.4.5.2 Balra toló operátor(<<):Ez az operátor a szám bitjeit balra tolja a megadott mértékkel. Egy bittel való eltolás, 2-vel való szorzásnak felel meg. Nézzünk egy példát: 00000000000000000000000000001000//8<<200000000000000000000000000100000//32[/quote]Itt megint könnyedén észrevehetõ mi is történt. Minden bitet fentebb \"toltunk\" 2-vel. Azok a bitek, amelyek ezáltal a 32. bit elé kerültek, kiesnek. Az így megüresedett bitek 0-kkal egészültek ki. Eddig nem túl bonyolult. Azonban figyeljünk oda, ha túl sokáig shiftelünk egy pozitív számot, akkor az utolsó érték, melyet fel tud venni a változónk (mielõtt még túlcsordulás miatt elérné a 0-t), az egy negatív szám!Nézzük mi történik negatív számok esetén: 11111111111111111111111111111000//-8<<211111111111111111111111111100000//-32[/quote]Láthatjuk hogy a balra shifteléskor a szám mindig 0-kkal egészítõdik ki, legyen az akár negatív, akár pozitív. Nem veszi figyelembe az MSB értékét, így ha túl sokat shiftelünk, akkor elõfordulhat, hogy a szám elveszti negatív értékét, és eléri a 0-t.1.4.6 Logikai biteltolás (logical shift, logikai shiftelés) operátorElõször is le kell tisztázni, hogy logikai shiftelésbõl csak egy létezik, jobbra logikai shiftelés(>>>). Sokban hasonlít a jobbra shiftelõ operátorra (>>), azonban van egy fontos különbség.Nézzük az elsõ példát: 00000000000000000000000000001000//8>>>200000000000000000000000000000010//2[/quote]Eddig nem igazán tapasztalhattunk semmi változást, minden pont ugyanúgy zajlott, mint a jobbra shiftelésnél(>>).Nézzük mi történik negatív szám esetén: 11111111111111111111111111111000//-8>>>200111111111111111111111111111110 //1073741822[/quote]Itt már láthatjuk a különbséget. Logikai shiftelés esetén az üres bitek helyére bekerülõ érték nem függ az MSB-tõl, hanem mindig 0. Emiatt soha nem kaphatunk eredményül negatív számot, logikai shiftelés esetén.1.5 Elsõ fejezet végeNos, ezennel átvettünk mindent, amit érdemes tudni a bitmûveletekrõl. Megmutattam az alapok, hogy hogyan épülnek fel a változók, valamint átvettük a fõ operátorokat. Eddig csak színtiszta elmélet volt. A következõ részben megmutatom, hogyan hasznosíthatjuk mindezt a gyakorlatban, vagyis mire is jó az a sok marhaság, amit eddig leírtam.2. Gyakorlati példák, érdekességek 2.1 Áttekintés, mikrõl is lesz még szóAz alábbiakban még pár dolgot szeretnék mutatni. Elõször is néhány \"függvénnyel\" fogok foglalkozni, majd mutatok még egy-két érdekességet. Konkrétan megmutatom, hogyan tudjuk egy jármû minden \"paneljének\" adatát kiíratni, részletesen(erre alapból nincs lehetõség, és a függvények is mind \"encode\" függvények, én viszont mutatok egy-egy decode \"verziót\" is.)Az alábbi 4 függvénnyel fogok foglalkozni: encode_tiresencode_panelsencode_doorsencode_lightsDe elõbb egy kis háttérinfó:A Pawnban a forrásban megadhatunk bináris számokat ugyanúgy, ahogyan Hex számokat is megadhatunk. Ehhez a \"0b\" prefixet kell használnunk. Tehát ha bináris számunk az 1011 és ezt akarjuk adni értékül egy változónak, azt így tehetjük meg: new var = 0b1011; 2.2 encode_tiresHáttér információ:A SA:MP-ban egy jármû abroncsainak adataid 4 bit tárolja. Az elsõ bit a jobb hátsó, a második bit a jobb elsõ, a harmadik bit a bal hátsó, a negyedik bit pedig a bal elsõ abroncsról tárol információt. Mivel 4 bitben eltárolhatóak az információk, így decimális formában 0-15 értékekkel is be lehet állítani a jármûvek abroncsait. 16-tól kezdve az értékek már ismétlõdni kezdenek, szóval a 16-nál az eredmény ugyanaz mint 0-nál, 17-nél ugyanaz mind 1-nél, és így tovább. Ez érthetõ, ha tudjuk hogyan épül fel a kettes számrendszer.(Ne felejtsük el, a biteket hátulról számozzuk!)Nos nézzük hogyan néz ki a kód: encode_tires(tire1, tire2, tire3, tire4){ return tire1 | tire2 << 1 | tire3 << 2 | tire4 << 3;} Láthatjuk, hogy bekérjük a 4 abroncs adatát(vagyis hogy ép[0], vagy kidurrant[1]), majd egy sima bitenkénti VAGY mûvelet és biteltolás kombinálásával visszatérünk az eredménnyel. A kódban a tire1 felel meg a jobb hátsó abroncsnak, a tire2 a jobb elsõ, a tire3 a bal hátsó, és a tire4 a bal elsõnek. Nézzünk egy konkrét példát. Tegyük fel, hogy én a hátsó 2 abroncsot ki akarom durrantani, még az elsõ kettõt épre akarom beállítani. Ez esetben így néz ki a dolog: encode_tire(1,0,1,0); Ha kiíratnánk konzolba a visszatérési értéket (%d-vel), 5-öt kapnánk. Miért is? Az 5 bináris alakja 0101. Hoppá, ha megnézzük ez pont fordítottja annak, amit mi megadtunk számjegyekként, csak már konkrét bináris számként. Nézzük hogyan is történhetett mindez, mit is csináltunk. A függvény a következõ mûveletet végzi: tire1 | tire2 << 1 | tire3 << 2 | tire4 << 3 Konyhanyelven úgy magyarázhatnánk el a dolgot, hogy ez a kód egymás mellé pakolgatja a megadott számokat, fordított sorrendben. Na de mi most nem a konyhanyelv miatt vagyunk ebben a leírásban, szóval nézzük a kódunkat. Lényegében ez futott le, a konkrét értékeinkkel nézve: 1 | 0 << 1 | 1 << 2 | 0 << 3 Bármily furcsa is, ez egy mûvelet, melyen szépen végig halad sorban a program. Elõször is írjuk át az egészet bináris alakba(az egyszerûség kedvéért most csak 4 bitet fogok kiírni): 0001 | 0000 << 1 | 0001 << 2 | 0000 << 3 Na erre se mondtuk volna rá a tutorial elsõ részének elolvasása elõtt, hogy ez egy mûveletsor. A rendszer elsõnek az eltolásokat végzi el (olyan ez mint a 2*3+2*4+2*5 mûveletben elõször a szorzásokat végezzük el, és csak utána adunk össze, ha nincs zárójel), és csak utána a bitenkénti VAGY mûveleteket, ezért mi is így fogunk tenni. Emlékezzünk, eltoláskor az \"üres\" bitek 0 értéket fognak felvenni. Végezzük el külön csak az eltolásokat: 0000 << 1 = 00000001 << 2 = 01000000 << 3 0000 Nos, ha ezeket elvégeztük, akkor nézzük, hogy is áll most a kódunk: 0001 | 0000 | 0100 | 0000 Négy azonos mûvelet van hátra, így hát a program innen más szépen sorban fog haladni. Tegyünk mi is így. Elsõ tehát a 0001 | 0000 mûvelet elvégzése. Emlékeztetõül, a bitenkénti VAGY mûvelet akkor ad 0-t eredményül, ha mind a kettõ érték 0, minden más esetben 1-et. Végezzük el külön az elsõ bitenkénti VAGY mûveletet: 0001 | 0000 = 0001 Nem volt bonyolult, nézzük tehát hogy áll akkor most a teljes mûveletünk: 0001 | 0100 | 0000 Haladunk szépen tovább, elvégezzük a 0001 | 0100 mûveletet. 0001 | 0100 = 0101 Nincs mit magyarázni rajta, egyszerû bitenkénti VAGY mûvelet volt ez is. Már csak ez van hátra: 0101 | 0000 Nos, ez sem lesz egy bonyolult mûvelet: 0101 | 0000 = 0101 Ezzel meg is kaptuk a végeredményt, ami nem más, mint a 0101 (tízes számrendszerben 5). Mivel azonban eléggé kusza lett így a számításunk, nézzük egyben hogyan is néz ki az egész, ha nem bontom ennyire részekre: 0100 | 0000 << 1 | 0001 << 2 | 0000 << 3v0001 | 0000 | 0100 | 0000v0001 | 0100 | 0000v0101 | 0000v0101 Nos, ezennel láthattuk a gyakorlatban is, hogyan mûködik a függvény. Kijelenthetjük tehát, hogy a függvény a számokat a sorrendjüknek megfelelõ számú bitbe helyezi. Mivel azonban a biteket hátulról számozzuk, így az 1,0,1,0 bemenetek végeredménye 0101 lesz. Így már nem is olyan bonyolult Na, ha már ilyen sokáig eljutottunk, akkor felhasználhatjuk ezt a kódot arra, hogy az alapjait figyelembe véve elkészítsük a decode változatot. Mivel 4 értékkel térünk vissza, ezért paraméterként kell majd 4 változó, melyeknek módosíthatjuk az értékét, illetve 1 paraméter pedig a dekódolandó szám. A függvényünk \"címsora\" tehát így néz ki: decode_tires(tires, &tire1, &tire2, &tire3, &tire4) Hogy példánk ne csak elmélet legyen, a tires változó legyen az elõbbi 0101 értékkel egyenlõ. Na akkor menjünk tovább. Bemeneti értékünk ugye a \"tires\" változóban lesz eltárolva, így ezzel fogjuk a mûveleteket végezni. Kezdjünk el gondolkozni. Hogyan is tudnánk ellenõrizni egy adatot. Legegyszerûbb ha a bitenkénti ÉS mûveletet alkalmazzuk. Miért is? Nos, a bitenkénti ÉS mûvelet ugye akkor ad eredményül 1-et, ha mind a 2 érték egy. Tehát ha min egy számot összehasonlítunk a 1-el(binárisan ugye 00000000000000000000000000000001, akkor az eredményünk lényegében arról fog tájékoztatni, hogy az elsõ bit 0, vagy 1. Nem is olyan bonyolult ez. Nézzük akkor az elsõ bitet. Arra emlékezhetünk még, hogy az elsõ bit a tire1, vagyis jobb hátsó abroncs adatait tárolja. Tehát: tire1 = tires & 1; Vagy konkrétan a példánk esetében: tire1 = 0101 & 0001 Mivel a bitenkénti ÉS mûvelet akkor ad 1-et eredményül, ha mind a kettõ bit 1, akkor már láthatjuk is, hogy miért elegendõ az elsõ bitet szemügyre venni. Mivel mind a 2 helyen az elsõ bit 1, ezért a tire1 változónk értéke is 1(0001) lesz. Egyszerû és nagyszerû, máris megkaptuk a jobb hátsó abroncs állapotát.Gondolkozzunk hát tovább. Nekünk most arra van szükségünk, hogy a 2. bit értékét megkapjuk. Hogyan kaphatjuk ezt meg? Legegyszerûbb megoldás, ha a tires változót jobbra shifteljük 1-el, majd ismételten bitenkénti ÉS mûveletet végzünk. Nézzük tehát, hogyan is nézne ez ki: tire2= tires >> 1 & 1; Vagy konkrétan a mi példánkban: tire2 = 0101 >> 1 & 0001vtire2 = 0010 & 0001 Ismételten bitenkénti ÉS mûvelet, és az eredményünk 0, mivel csak az egyik bit 1, a másik 0, így a bitenkénti ÉS mûvelet eredménye is 0. Ezen az elven megcsináljuk a maradék 2 abronccsal is: tire3= tires >> 2 & 1;tire4= tires >> 3 & 1; Vagy a mi esetünkben konkrét példánkkal: tire3 = 0101 >> 2 & 0001 // azaz: --> 0001 & 0001 = 1tire4 = 0101 >> 3 & 0001 // azaz: --> 0000 & 0001 = 0 A teljes kódunk tehát: decode_tires(tires, &tire1, &tire2, &tire3, &tire4){ tire1 = tires & 1; tire2 = tires >> 1 & 1; tire3 = tires >> 2 & 1; tire4 = tires >> 3 & 1;} A konkrét példánkban a 4 változónk értéke a függvény lefutása után: tire1: 1tire2: 0tire3: 1tire4: 0 Ha összehasonlítjuk, ezek pont azok az értékek, mint amiket az encode függvényben megadtunk, tehát úgy tûnik jól dolgoztunk 2.3 encode_lightsEz a rész lesz a legrövidebb. Az egész lámpás rész pontosan ugyanarra a sémára épül mint az abroncsok. Az adott bitek ugyanazon a \"helyen\" lévõ lámpa adatait szolgáltatják, ugyanúgy 0 az ép, és 1 a sérült lámpák állapot azonosítója, és ugyanúgy 1-1 bit tárolja az értékeket.Maga a kód: encode_lights(light1, light2, light3, light4){ return light1 | light2 << 1 | light3 << 2 | light4 << 3;} Láthatjuk hogy halál pontosan ugyanaz mint az encode_tires, csak más változónevekkel. Így hát a decode függvény is ugyan az lesz, csak a változóneveket kell módosítanunk: decode_lights(lights, &light1, &light2, &light3, &light4){ light1 = lights & 1; light2 = lights >> 1 & 1; light3 = lights >> 2 & 1; light4 = lights >> 3 & 1;} Ha megnézzük látjuk, hogy teljesen ugyan az a séma, ugyan az a felépítés, minden megegyezik, ezért nem is írtam le újból ugyanazt, amit egyszer már kifejtettem.2.4 encode_panelsHáttér információ:A SA:MP 7 panelrõl tárol adatokat. Egy panel adatait 4 bit írja le. Nos, ennél több infóra nekünk most nincs szükségünk, az elõzõ kód alapján ezen kód is megérthetõ, így az encode verzióhoz nem is nagyon szeretnék magyarázatot fûzni. A különbség az encode_tires-hez képest, hogy itt 4-essével shiftelünk, mivel 1 panel adatait 4 bit tárolja.Maga a kód: encode_panels(flp, frp, rlp, rrp, windshield, front_bumper, rear_bumper){ return flp | frp << 4 | rlp << 8 | rrp << 12 | windshield << 16 | front_bumper << 20 | rear_bumper << 24;} Nézzünk egy decode verziót, ehhez a kódhoz. Alapjaiban ugyanazon a vonalon kell elindulnunk, mint a decode_tires esetében. Itt most 7 visszatérési értékünk lesz, és 1 bemeneti érték. Tehát így néz ki a függvény header: decode_panels(panels, &flp, &frp, &rlp, &rrp, &windshield, &front_bumper, &rear_bumper) A folytatásban megint csak az elõbbi verziót kell alapul vennünk, azonban itt lesz egy kis különbség. Egyértelmû, hogy mivel 1 panel adatait 4 bit tárolja, így a 4 bitet nekünk egyben kell lekérni, és egyben kell vele visszatérnünk. Megint csak a bitenkénti ÉS mûveletet fogjuk alkalmazni, azonban mivel 4 bitrõl van szó, így egy olyan számmal kell összehasonlítanunk, mely bináris alakban 1111. Ez a szám nem más, mint a 15. Hogy miért ez kell?Ha egy bitsorozat tartalmát ki szeretnénk másolni egy másik változóba, akkor a bitenkénti ÉS mûveletre vna szükségünk. A bitenkénti ÉS mûvelet akkor ad 1-et eredményül, ha mindkét bit 1, ha valamelyik 0, akkor a visszatérés már 0 lesz. Ergó tehát, ha egy csupa 1-esekbõl álló bitsorozattal vetjük össze az eredeti bitsorozatunkat, akkor lényegében lemásoljuk a sorozatot, egy másik változóba, hisz eredményül mindig az eredeti sozatot fogjuk visszakapni. Íme néhány példa: 0101 & 1111 = 01011111 & 1111 = 11110000 & 1111 = 00001011 & 1111 = 1011 Láthatjuk tehát, hogy ha csupa 1-est tartalmazó bitsorozattal hasonlítjuk össze az eredeti számunkat, akkor lényegében egy másolatot készítünk. Mivel nekünk erre a 4 bitre van szükségünk a 32-bõl, így pontosan ezt kell tennünk, ezen 4 bitet lemásolni egy másik változóba, hogy késõbb ezt majd egy másik függvénnyel még jobban darabokra szedhessük.Íme tehát akkor a kódunk elsõ sora: flp = panels & 15; Mivel 4-esével vizsgáljuk a biteket, így lényegében annyit kell még változtatnunk az elõzõ kódon(decode_tires), hogy az 1-esével növekvõ shiftelések helyett 4-esével shifteljünk. Azaz a 2. panel adatainak lekérdezése: frp = panels >> 4 & 15; Innen pedig már csak mindig további 4 shiftelést el kell végeznünk, és szépen végig haladhatunk a panels változó bitjein, és megkaphatjuk minden panel adatait külön-külön. rlp = panels >> 8 & 15;rrp = panels >> 12 & 15;windshield = panels >> 16 & 15;front_bumper= panels >> 20 & 15;rear_bumper = panels >> 24 & 15; Ezennel minden panel adatait megkaptuk. Ezután már akár a decode_tires függvényt felhasználva szét lehet szedni az adatokat darabokra, de akár egy saját névvel, és más változó nevekkel ellátott másik függvényt is létre lehet hozni a decode_tires mintájára.Tehát az egész kód egyben: decode_panels(panels, &flp, &frp, &rlp, &rrp, &windshield, &front_bumper, &rear_bumper){ flp = panels & 15; frp = panels >> 4 & 15; rlp = panels >> 8 & 15; rrp = panels >> 12 & 15; windshield = panels >> 16 & 15; front_bumper= panels >> 20 & 15; rear_bumper = panels >> 24 & 15;} Igazából a függvény ugyanarra a sémára épül mint a decode_tires, csak annyi a különbség, hogy 4 bitenként kapjuk az adatokat, nem pedig egyesével, mivel itt minden panelnek 4 adatát tudjuk majd még lekérdezni. Nem tudom lehet-e még ehhez a részhez hozzáfûzni valamit, szóval szerintem ugorjunk. 2.5 encode_doorsHáttér információ:A SA:MP képes minden jármû 4 \"ajtajának\" állapotát tárolni. Ezek az \"ajtók\"(Doors-t hogy fordíthatnám jobban?) konkrétan a motorháztetõ, csomagtartó, vezetõoldali ajtó, és anyósülés oldali ajtó. A 2 hátsó ajtó állapotát jelenleg még nincs lehetõségünk lekérdezni.Amikor lekérdezzük egy jármû \"ajtajának\" adatait, azt a SA:MP 4 bájtként(32 bit) adja vissza. Ebbõl a 4 bájtból 1 bájt(8 bit) jut minden egyes objektumra. Vagyis minden egyes objektum adatait 8 bit tárolja. Vagyis tárolhatná...A SA:MP csak az elsõ 3 bitet használja, a további bitek üresek lesznek, nincs funkciójuk, talán majd késõbb lesz. Az elsõ bit azt tárolja, hogy az objektum zárt[0], vagy nyitott[1](nem az jelenti, hogy bezárt, hanem becsukott/kinyitott) állapotban van-e. A második bit azt adja meg, hogy az objektum ép[0] vagy sérült[1]. A 3. bit arról tájékoztat, hogy az adott objektum rajta van-e még a jármûvön[0] vagy már leesett[1]. A többi bit mint mondtam üres. Ez majd a decode függvénynél lesz fontos.Nézzük tehát a konkrét kódot: encode_doors(bonnet, boot, driver_door, passenger_door, behind_driver_door, behind_passenger_door){ #pragma unused behind_driver_door #pragma unused behind_passenger_door return bonnet | boot << 8 | driver_door << 16 | passenger_door << 24;} A 2 hátsó ajtóval nem foglalkozunk, mivel azok jelenleg még nem támogatottak ugyebár, de a kód készítõje gondolt a jövõre is, vagy fogalmam sincs miért adta meg ezt a két paramétert is, hisz ígyis-úgyis módosítani kell a kódon... Ha behoznak még 2 ajtót, akkor az 1 bájtos tárolással már 6 bájt lenne, de 1 változó maximum 4 bájt lehet, tehát a kódot úgyis módosítani kell, ha meg arra gondoltak, hogy legalább a paramétereket nem kell majd szegény scriptereknek minden sorban átírni, az megint hülyeség, hisz ki adna értéket olyan paraméternek, amely nincs használva?...Nos mindegy, nem a kritizálásért vagyunk most itt. Ha megnézzük, a kód 8-as shiftelést használ, mivel egy objektum adatait 8 bit tárolja. A mûködési elv ugyanaz, mint az elõzõ encode függvényeknél, így erre nem kívánok megint kitérni.Nézzük azonban hogyan írnánk ehhez egy decode függvényt. Mikor megkérdeztem errõl ismerõsömet, õ azt mondta, ugyanúgy mint az elõzõ, decode_panels függvénynél, csak 8-as shiftelést használunk, és 15 helyett 255-el végezzük a bitenkénti ÉS mûveletet. 255-el? minek?No erre is ki fogok térni késõbb, de haladjunk a szokásos módon. 1 bemeneti paraméterünk van, és 4 kimeneti, vagyis a header rész a következõ: decode_doors(doors, &bonnet, &boot, &driver_door, &passenger_door) Ezzel meg is vagyunk, itt nem látok különösebb magyarázni valót. Én most nem fogok törõdni a 2 nem létezõ ajtóval, ha egy jövõbeni verzióban netán azokat is behoznák mint lekérdezhetõ objektum, akkor ígyis-úgyis módosítani kell majd a kódot, addig meg úgysincs rá szükségünk.Nos, mint az eddigi decode függvényeknél, elsõ feladatunk egy shiftelés mentes bitenkénti ÉS mûvelet elvégzése. Az egyik megoldás a következõ: bonnet = doors & 255; Lehet így is. Azonban véleményem szerint felesleges itt egy ekkora számmal \"szenvednünk\". Miért használjunk ekkora számot, és ezáltal bonyolítsuk feleslegesen a kódunkat, ha nekünk a 8 bitbõl csak az elsõ 3 lesz ami adatot tárol? Bõven elegendõ egy olyan szám, amely bináris alakja 111. Ez nem más, mint a 7. Tehát a következõ megoldás ugyanúgy helyes: bonnet = doors & 7; A két kód pontosan ugyanazt az eredményt fogja visszaadni, semmi különbség a kettõ között. Csupán elsõ esetében nagy számmal dolgozunk, még második verzió esetén csak akkorával, amekkora szükséges.A kód többi részében csak 8-as shifteléssel végigcammogunk a doors változó többi bitjén is: boot = doors >> 8 & 7;driver_door = doors >> 16 & 7;passenger_door = doors >> 24 & 7; A teljes kód egyben: decode_doors(doors, &bonnet, &boot, &driver_door, &passenger_door){ bonnet = doors & 7; boot = doors >> 8 & 7; driver_door = doors >> 16 & 7; passenger_door = doors >> 24 & 7;} A kód pont ugyanazon az elven épül fel, mint az összes többi decode függvényünk, csak épp a benne használatos értékek az \"ajtó\" objectumok adatstruktúrájához vannak igazítva.2.6 Egyéb ötletek gyakorlati alkalmazásra, hasznosításraJoggal felmerülhet a kérdés, hogy ennyi? Emiatt rágtuk végig magunkat ezen néhány tízezer karakteren? Nem. Nem ennyi. A SA:MP-ban rengeteg felhasználási módja lehet még a bitmûveleteknek. Mutatok is néhány dolgot, mire jók még.2.6.1 RGBA->HEX; HEX->RGBA konvertálásokNos, bármily \"hihetetlen\", erre is használható. Miért is? Nos, 16-os számrendszerben 0-9, valamint A-F \"szánjegyek\" léteznek. A hexadecimális számjegy decimális értékben 0-15 lehet. Ha megnézzük bináris értékeket, 0-15 az 4 bittel leírható, hiszen a 15 az bináris alakban 1111. De ha józan paraszti ésszel belegondolunk is. 16 az 24. Így tehát 1 hexadecimális érték, az 4 bináris értékkel írható le. Ebbõl már sejthetõ, hogy itt is shiftelésre lesz szükségünk. Naná Mivel 4 bit ír le egy hexadecimális számjegyet, így 4-esével kell majd shiftelnünk.Mi is az az RGBA?RGBA: A színek olyan meghatározási módja, melyben a 3 alapszín([r]ed, [g]reen, lue) mellett egy 4. érték, az úgynevezett [a]lpha is szerepel, amely az áttetszõséget adja meg. A legtöbb program(pl. paint, PS), és pl. a css(külön függvénnyel!) is elfogadja ezen értékeket decimális számokként(0-255) is, a SA:MP-ban azonban hexadecimális alakban kell megadnunk. Bármily furcsa a következõ 4 sor pont ugyan azt jelenti:\"Normál\" RGB alak(pl. paint, photoshop): Piros: 0, Zöld: 255, Kék: 255CSS alak(függvénnyel): rgba(0, 255, 255, 1);Pawn alak(1): 0x00FFFFFFPawn alak(2): 16777215 Ó igen, a Pawn-ban decimális számokként is megadhatjuk a színeket, hisz a program mindent bináris számrendszerbe fog átszámolni. De akár bináris alakban is megadhattuk volna:Pawn alak(3): 0b111111111111111111111111 Az, hogy mi a Pawn-nak hexadecimális alakban szoktuk megadni, annak az egyetlen oka, hogy így a leginkább \"átlátható\", és ez egy amúgy is elfogadott módja (mármint a hexadecimális számmal való megadás) a színek leírásának a számítástechnikában.Nos, mivel 1 szín 0 és 255 közötti értéket tartalmaz a piros, zöld és kék színekbõl, ez hexadecimálisan 2 számjeggyel leírható: 00-FF. Az FF értéke 255, aki nem hiszi, számoljon utána (162-1)2 hexadecimális számjegy az pedig 8 bináris számjeggyel leírható(00000000-11111111). Így már láthatjuk, hogy 8-as shiftelésre lesz szükségünk. Másra nincs is szükségünk, az RGBA->HEX konverter elkészítéséhez.stock RGBAToHex( r, g, b, a ){return a | b << 8 | g << 16 | r << 24;} Az, hogy miért fordított sorrendben kellett megadnunk az értékeket, az megint csak azzal magyarázható, hogy a biteket hétköznapi értelemben véve visszafelé számozzuk.Egy ellentét, azaz HexToRGBA függvény megírása sem túl bonyolult, ugyan ezt kell csinálnunk, csak visszafelé.stock HexToRGBA( colour, &r, &g, &b, &a ){a = colour & 0xFF;b = (colour >> 8 ) & 0xFF;g = (colour >> 16 ) & 0xFF;r = (colour >> 24 ) & 0xFF;} A 0xFF-et természetesen decimális értékkel is megadhatjuk(255).No akkor lehet ugye RGBA-t ARGB-be is konvertálni, az így néz ki:RGBAToARGBHEX(r,g,b,a){return (r & 0xff) << 24 | (g & 0xff) << 16 | (b & 0xff) << 8 | a & 0xff;} Ez máris egy újabb felhasználási módja a bitmûveleteknek. De, mutatok még 2.6.2 Boolen változók helyett IntegerAz al-al-alcímet elolvasva joggal felmerülhet a kérdés, hogy ez meg hogy jön ide. Nos ez úgy jön ide, hogy most egy kis memóriatakarékosságról fogok itt regélni. Adhattam volna azt is címnek, hogy miért ne használjunk boolean változókat, ha legalább 5 igaz/hamis értéket el akarunk tárolni.Nos akkor egy kis memórialecke. Azt már tisztáztuk, hogy az integer változók 32 bitesek, vagyis 4 bájtot foglalnak le a memóriában maguknak. A boolean típusú változókról azonban még nem volt szó. Mivel 1 értéket tárolnak csak, az is csak igaz vagy hamis lehet, így joggal gondolhatnánk, hogy 1 bit a mérete. Azonban nem...1 boolean változó 8 bitet, azaz 1 bájtot foglal le magának a memóriában. Hogy ez miért így van, az a számítógépek memóriahasználatával magyarázható. Manapság nincs mód arra, hogy a memóriában egyetlen bitet módosítsunk, csak 8-as \"csomagokban\", vagyis bájtonként tudjuk ezt megtenni. Hogy ez miért van így, azt most nem fogom itt fejtegetni Nos, ha megnézzük tehát, a boolean változó egy nagyon-nagyon pazarló megoldás. Hiszen 1 darab boolean változó 1 bájtot foglal, 4 darab boolean változó esetén ez már 4 bájt, ami megegyezik 1 integer változó méretével. Azonban, még a 4 boolean változóban 4 igaz/hamis érték kerülhet tárolásra, addig egy integerben 32 igaz/hamis értéket is eltárolhatunk.Bizony, 32 igaz/hamis érték. Hiszen 1 integer változó 32 bitbõl áll, ha minden bitet hasznosítunk, akkor a bitmûveletek felhasználásával 32 igaz/hamis értéket is eltárolhatunk. Összehasonlítva:32 darab boolean változó mérete 32 bájt.1 integer változó mérete: 4 bájt. Vagyis 32 boolean változó esetén nyolcad annyi memóriánkba kerül, ha egy darab integerben tároljuk el az értékeket. Persze 32 booleanra ritkán van szükség, de egy RP modban simán elõfordulhat. Gondoljunk csak bele... Van 32 boolean-tömbös változónk, ez a maximum slotszámot(1000) figyelembe véve: 32*1000 byte azaz 32000 byte ami ~31 kilobyte. Ha ezt egyetlen integer-tömbbel oldanánk meg: 4*1000 byte, azaz 4000 byte, ami ~3,9 kilobyte. Azért így nézve már nem annyira \"elhanyagolandó\" az a különbség szerintem =)Akár írhatnánk erre külön egy függvényt, mely az adott bit értékével tér vissza. Nem is bonyolult. Így már azok számára is használhatóak a bitmûveletek, akik nem értenek hozzá, és rengeteg memóriát takaríthatnak meg.Íme egy függvény az adatok kiolvasására:stock GetData(integer, bit){ return integer >> bit & 1; //vagy az 1 helyett akár 0x01 alakban is megadhatjuk, nincs jelentõsége} És a párja, adat írásra:stock SetData(&integer, bit, value){ integer |= value << bit; return 1;} Persze ezt még lehetne csinosítani, hogy pl. ne 0-tól hanem 1-tõl számoljuk a biteket, vagy hogy 31(vagy 32 ha 1-tõl számolunk) felett 0 legyen a return értéke, esetleg a value-re is rakhatnánk ellenõrzést, hogy 0-1-e az értéke, vagy más, de ilyenekkel most nem törõdtem.2.6.3 Több érték, egy integerbeNa ez az a rész, ami véleményem szerint már csak a nagyon megrögzött memóriafelszabadítók szerint nem lesz felesleges Amennyiben nincs szükségünk túl nagy számokra(pl. csak 0-15, vagy akár csak 0-7, de akár még a 0-255 is megfelel), akkor akár egy integerbe is tehetjük õket. Hogyan? Egyszerû. Csoportosítjuk a biteket. Tegyük fel, hogy maradván egy RP szervernél, el akarjuk tárolni ûbergigantikusan memóriakímélõ megoldással a játékos házának számát(ez legyen mondjuk 0-100 közötti szám), a játékos frakcióját(ez legyen 0-15 között), a játékos rangját(0-7 között), hogy a telefonja be van-e kapcsolva(0-1), a játékos szolgálati jármûvének id-jét(legyen 0-255 között), és az alapmunkáját(0-15 között). Ez is megoldható, egyetlen integerben, és ismételten 1000 slottal számolva máris megspóroltunk 4*4*1000+1*1000 byte-t(5 integer, és 1 boolean helyett csupán 1 integert használunk), ami ~16,6 kilobyte. Azért nem mindegy az, hogy 20,5 kb-ot vagy 4 kb-ot használunk szerintem... Ezt a következõ módszerrel valósíthatnánk meg:#define DATA_HOUSE 0#define DATA_FACTION 1#define DATA_RANK 2#define DATA_PHONESTATE 3#define DATA_DUTYCAR 4#define DATA_JOB 5stock GetData(playerinfo, data){ switch(data) { case DATA_HOUSE: return playerinfo & 127;//7 bit case DATA_FACTION: return playerinfo >> 7 & 15;//4 bit case DATA_RANK: return playerinfo >> 11 & 7;//3 bit case DATA_PHONESTATE: return playerinfo >> 14 & 1;//1 bit case DATA_DUTYCAR: return playerinfo >> 15 & 255;//8 bit case DATA_JOB: return playerinfo >> 23 & 15;//4 bit}return -1;}stock SetData(&playerinfo, data, value){ switch(data) { case DATA_HOUSE: { playerinfo = playerinfo & ~127 | value; }//7 bit (0-127 közötti maximálisan) case DATA_FACTION: { playerinfo = playerinfo & ~(15 << 7) | value << 7; }//4 bit (0-15 között) case DATA_RANK: { playerinfo = playerinfo & ~(7 << 11) | value << 11; }//3 bit (0-7 között) case DATA_PHONESTATE: { playerinfo = playerinfo & ~(1 << 14) | value << 14; }//1 bit (0-1) case DATA_DUTYCAR: { playerinfo = playerinfo & ~(255 << 15) | value << 15; }//8 bit (0-255 között) case DATA_JOB: { playerinfo = playerinfo & ~(15 << 23) | value << 23; }//4 bit (0-15 között) } return -1;}stock SetDatas(&playerinfo, house, faction, rank, phonestate, dutycar, job){playerinfo |= house | (faction << 7) | (rank << 11) | (phonestate << 14) | (dutycar << 15) | (job << 23);} Ez már azért elég morbid, ezt inkább csak érdekességképp mutattam, de éppenséggel ez is használható lenne, csak ehhez már kell egy kis õrültség 2.7 Második fejezet végeNos, láthattuk hogyan is mûködik mindazon sok \"marhaság\", amit az elsõ fejezetben elméletként átvettünk. Láthattuk hogyan mûködnek a jármûvek paneljének adataival babráló encode függvények, valamint megmutattam, hogyan írhatunk hozzájuk decode verziókat. Láthattunk még példát egyéb gyakorlati hasznosításra, kicsit megkoccoltuk a hexadecimális számok világát is, majd szóba került egy kis memóriatakarékosság. Végül mutattam egy összetettebb, elvetemült példát is, a bitmûveletek hasznosítására.3. Összegzés, végszó Úgy gondolom, hogy elég részletesen körbe jártuk a témát. Megnéztük a bitmûveletek elméleti, és gyakorlati oldalát is, láthattunk gyakorlati példákat. Nem kell elsõre megérteni ezt az egészet, sõt... Elegendõ ha egyszer elolvassuk, emésztjük kicsit, majd késõbb megint elõvesszük, akkor netán már kicsit többet is megértünk belõle. Nem egy kihagyhatatlan része a Pawno-nak, de azért van annak haszna, ha az ember jártas ebben a témában is, bár sokak szerint nem éri meg a sok fáradtságot, és idõt, mely a megértéséhez, begyakorlásához, használatának elsajátításához szükséges. Próbáltam mindent részletesen kifejteni. A bonyolultabb dolgokhoz sokat magyarázni, az egyszerûbbek esetén csak visszautalni az alapokhoz. Sok dolgot talán már túl is magyaráztam. Ezt a leírást nem 1 nap alatt készítettem el, kb. 1 hónapja írtam meg az elméleti oldalát, most az elmúlt pár napban pedig a gyakorlatot. Esténként 1-2 órát szántam rá csupán, és mivel ilyenkor már nem vagyok épp a toppon, elképzelhetõ, hogy néhány helyen helyesírási, vagy elvi hibát vétettem. Amennyiben valaki ilyet találna, kérem szóljon, és korrigálni fogom. Nos, azt hiszem nincs is más hátra, mint hogy sok szerencsét kívánjak a bitmûveletek elsajátításához, és megosszam a forrásokat. Remélem néhányan hasznosnak találják, eme igen hosszúra sikerült leírást.4. Források: Internet:WikipédiaSA:MP WikiSA:MP hivatalos fórumKönyv, e-book:Pohl László - A programozás alapjai(BME - Elektronikus Eszközök Tanszéke, Budapest, 2010)Siroki László - C programozás - Bitmûveletek jegyzeteˆ Anthony, 2012 - Minden jog fenntartva.A leírás egészben, vagy részleteiben szabadon terjeszthetõ, ameddig egyértelmûen fel van tüntetve az eredeti készítõ, valamint hogy honnan is származik. « Utoljára szerkesztve: 2012. augusztus 01. - 10:53:54 írta Anthony » Naplózva
Bin:00000000000000000000000000001011&00000000000000000000000000000111=00000000000000000000000000000011Dec:11&7=3[/quote]Még néhány példa, de ezeket már nem részletezem: 00000000000000000000000000000011 & 00000000000000000000000000000101 = 0000000000000000000000000000000100000000000000000000000001110110 & 00000000000000000000000001011001 = 00000000000000000000000001010000[/quote]1.4.2 Bitenkénti (bitek közötti) VAGY mûveletA bitenkénti VAGY mûvelet (OR, |) egy logikai operátor. Fontos, hogy nem összekeverendõ a \"||\" logikai operátorral! Hasonlít rá, de ez a mûveleteket bitenként végzi. Ez az operátor akkor ad eredményül 1-et, ha legalább az egyik bit 1. Ha egyik bit sem 1, akkor 0-t ad eredményül, ha mindkét bit 1, akkor is 1 az eredmény. Igazság táblázata: A B A|B000101011111[/quote]Példának nézzük meg, mi történik, ha a bitenkénti VAGY operátort alkalmazzuk, szintén a 11 és a 7 esetén. A 11 bináris számrendszerben mint már írtam 00000000000000000000000000001011-nek felel meg, a 7 pedig 00000000000000000000000000000111-nek. Tehát a 11 | 7, az ugyaz mint ha ezt írnám: 00000000000000000000000000001011 | 00000000000000000000000000000111. Az átláthatóság kedvéért ismét egymás alá fogom írni a 2 számot, hogy áttekinthetõbb legyen, és az eredményt pedig a 3. sorba: 00000000000000000000000000001011|0000000000000000000000000000011100000000000000000000000000001111[/quote]Eredményül tehát 00000000000000000000000000001111-et kaptunk, ami a kettes számrendszerben a 15-nekk felel meg. A következõ 2 sor tehát ugyanazt jelenti: Bin:00000000000000000000000000001011|00000000000000000000000000000111=00000000000000000000000000001111Dec:11|7=15[/quote]Megint néhány példa: 00000000000000000000000000000011 | 00000000000000000000000000000101 = 0000000000000000000000000000011100000000000000000000000001110110 | 00000000000000000000000001011001 = 00000000000000000000000001111111[/quote]1.4.3 Bitenkénti (bitek közötti) KIZÁRÓ VAGY mûveletA bitenkénti KIZÁRÓ VAGY mûvelet (XOR, ^) egy logikai operátor. Ez az operátor nagyon hasonlít a VAGY operátorra, az a fõ különbség a kettõ között, hogy amennyiben mindkét bit 1-es, akkor a KIZÁRÓ VAGY operátor 0-t ad eredményül, a többi esetben ugyanúgy viselkedik mint a VAGY operátor. Igazság táblázata: A B A^B000101011110[/quote]Példának nézzük meg, mi történik, ha a bitenkénti KIZÁRÓ VAGY operátort alkalmazzuk, szintén a 11 és a 7 esetén. A 11 bináris számrendszerben mint már írtam 00000000000000000000000000001011-nek felel meg, a 7 pedig 00000000000000000000000000000111-nek. Tehát a 11 ^ 7, az ugyaz mint ha ezt írnám: 00000000000000000000000000001011 ^ 00000000000000000000000000000111. Az átláthatóság kedvéért ismét egymás alá fogom írni a 2 számot, hogy áttekinthetõbb legyen, és az eredményt pedig a 3. sorba: 00000000000000000000000000001011^0000000000000000000000000000011100000000000000000000000000001100[/quote]Eredményül most 00000000000000000000000000001100-t kaptunk, ami a kettes számrendszerben a 12-nek felel meg. Érdekes, hogy teljesen más eredményt kapunk, pedig lényegében ugyanazt az eredményt adja az operátor, - kis különbséggel ugyebár - mint a VAGY operátor.A következõ 2 sor tehát ugyanazt jelenti: Bin:00000000000000000000000000001011^00000000000000000000000000000111=00000000000000000000000000001100Dec:11^7=12[/quote]Megint néhány példa: 00000000000000000000000000000011 ^ 00000000000000000000000000000101 = 0000000000000000000000000000011000000000000000000000000001110110 ^ 00000000000000000000000001011001 = 00000000000000000000000000101111[/quote]Ezzel kapcsolatban egy érdekességet mutatnék meg. Vegyünk 2 számot. A látványosság kedvéért én példának a 118-at és a 89-et veszem. 1. szám: 00000000000000000000000001110110 = 1182. szám: 00000000000000000000000001011001 = 89Mûvelet elvégzése: 00000000000000000000000001110110 ^ 00000000000000000000000001011001 = 00000000000000000000000000101111 = 471. szám az eredmény, 2. szám az eredeti 2. szám: 00000000000000000000000001011001 = 89Eredmény: 00000000000000000000000000101111 ^ 00000000000000000000000001011001 = 00000000000000000000000001110110 = 118[/quote]Vagyis visszakaptuk a kiindulási állapotot 1.4.4 Bitenkénti (bitek közötti) NEGÁLÁSA bitenkénti NEGÁLÁS (NOT, ~) az utolsó logikai operátor, melyet meg fogok mutatni. Ez az operátor eléggé eltér az eddigiektõl, leginkább abban, hogy ezt a mûveletet nem 2 bitre alkalmazzuk, hanem csak 1-re. Szokás komplementer-operátornak is hívni(konyhanyelven). Lényegében az 1-esekbõl 0-t, a 0-kból 1-est csinál. Igazság táblázata: A ~A0110[/quote]Példának nézzük meg, mi történik, ha a bitenkénti NEGÁLÁST alkalmazzuk a 7-es számra. A 7 bináris számrendszerben mint már írtam 00000000000000000000000000000111-nek felel meg. Tehát a ~7, az ugyaz mint ha ezt írnám: ~00000000000000000000000000000111. Nézzük tehát, mi történik: ~0000000000000000000000000000011111111111111111111111111111111000[/quote]Eredményül most 11111111111111111111111111111000-t kaptunk, ami a kettes számrendszerben a -8-nak felel meg. A következõ 2 sor tehát ugyanazt jelenti: Bin:~00000000000000000000000000000111=11111111111111111111111111111000Dec:~7=-8[/quote]Megint néhány példa: ~00000000000000000000000000000101 = 11111111111111111111111111111011~00000000000000000000000001110110= 11111111111111111111111110001010[/quote]Ehhez kapcsolódóan lenne itt egy fontos megjegyzendõ dolog. Aki nem érti, hogy miért -8 a megoldás, az nem olvasta el elég rendesen a \"Részletesebben a bitekrõl a Pawn-ban\" részt.1.4.5 Biteltolás (arithmetic shift, shiftelés) operátor(ok)A shift operátorok(<<, >>) a bitek tologatására használhatóak. A << balra tol, a >> jobbra. Bal oldalt található a szám, amit tologatni kívánunk, jobb oldalt pedig az, hogy mennyivel szeretnénk eltolni.1.4.5.1 Jobbra toló operátor(>>):Ez az operátor a szám bitjeit jobbra tolja a megadott mértékkel. Egy bittel való eltolás, 2-vel való osztásnak felel meg. Nézzünk egy példát: 00000000000000000000000000001000//8>>200000000000000000000000000000010//2[/quote]Láthatjuk, hogy semmi más nem történt, mint minden bitet 2-vel hátrébb mozgattunk. Az így \"megüresedett\" bitek helyére pedig 0-k kerültek. Lényegében az történt, hogy az utolsó 2 bit kiesett, az így megmaradt biteket 2-vel hátrébb csúsztattuk, majd az elsõ 2, így \"üres\" bit 0 értéket kapott.Nézzünk egy példát negatív számmal: 11111111111111111111111111110000//-8>>211111111111111111111111111111100//-2[/quote]Láthatjuk, hogy itt fontos szerepe van az MSB-nek. Ettõl függ ugyanis, hogy 0-k vagy 1-esek kerülnek a \"megüresedett\" bitek helyére. Fontos tehát megjegyezni, hogy a jobbra shiftelés megtartja a számok elõjelét, nem változtat azon!1.4.5.2 Balra toló operátor(<<):Ez az operátor a szám bitjeit balra tolja a megadott mértékkel. Egy bittel való eltolás, 2-vel való szorzásnak felel meg. Nézzünk egy példát: 00000000000000000000000000001000//8<<200000000000000000000000000100000//32[/quote]Itt megint könnyedén észrevehetõ mi is történt. Minden bitet fentebb \"toltunk\" 2-vel. Azok a bitek, amelyek ezáltal a 32. bit elé kerültek, kiesnek. Az így megüresedett bitek 0-kkal egészültek ki. Eddig nem túl bonyolult. Azonban figyeljünk oda, ha túl sokáig shiftelünk egy pozitív számot, akkor az utolsó érték, melyet fel tud venni a változónk (mielõtt még túlcsordulás miatt elérné a 0-t), az egy negatív szám!Nézzük mi történik negatív számok esetén: 11111111111111111111111111111000//-8<<211111111111111111111111111100000//-32[/quote]Láthatjuk hogy a balra shifteléskor a szám mindig 0-kkal egészítõdik ki, legyen az akár negatív, akár pozitív. Nem veszi figyelembe az MSB értékét, így ha túl sokat shiftelünk, akkor elõfordulhat, hogy a szám elveszti negatív értékét, és eléri a 0-t.1.4.6 Logikai biteltolás (logical shift, logikai shiftelés) operátorElõször is le kell tisztázni, hogy logikai shiftelésbõl csak egy létezik, jobbra logikai shiftelés(>>>). Sokban hasonlít a jobbra shiftelõ operátorra (>>), azonban van egy fontos különbség.Nézzük az elsõ példát: 00000000000000000000000000001000//8>>>200000000000000000000000000000010//2[/quote]Eddig nem igazán tapasztalhattunk semmi változást, minden pont ugyanúgy zajlott, mint a jobbra shiftelésnél(>>).Nézzük mi történik negatív szám esetén: 11111111111111111111111111111000//-8>>>200111111111111111111111111111110 //1073741822[/quote]Itt már láthatjuk a különbséget. Logikai shiftelés esetén az üres bitek helyére bekerülõ érték nem függ az MSB-tõl, hanem mindig 0. Emiatt soha nem kaphatunk eredményül negatív számot, logikai shiftelés esetén.1.5 Elsõ fejezet végeNos, ezennel átvettünk mindent, amit érdemes tudni a bitmûveletekrõl. Megmutattam az alapok, hogy hogyan épülnek fel a változók, valamint átvettük a fõ operátorokat. Eddig csak színtiszta elmélet volt. A következõ részben megmutatom, hogyan hasznosíthatjuk mindezt a gyakorlatban, vagyis mire is jó az a sok marhaság, amit eddig leírtam.2. Gyakorlati példák, érdekességek 2.1 Áttekintés, mikrõl is lesz még szóAz alábbiakban még pár dolgot szeretnék mutatni. Elõször is néhány \"függvénnyel\" fogok foglalkozni, majd mutatok még egy-két érdekességet. Konkrétan megmutatom, hogyan tudjuk egy jármû minden \"paneljének\" adatát kiíratni, részletesen(erre alapból nincs lehetõség, és a függvények is mind \"encode\" függvények, én viszont mutatok egy-egy decode \"verziót\" is.)Az alábbi 4 függvénnyel fogok foglalkozni: encode_tiresencode_panelsencode_doorsencode_lightsDe elõbb egy kis háttérinfó:A Pawnban a forrásban megadhatunk bináris számokat ugyanúgy, ahogyan Hex számokat is megadhatunk. Ehhez a \"0b\" prefixet kell használnunk. Tehát ha bináris számunk az 1011 és ezt akarjuk adni értékül egy változónak, azt így tehetjük meg: new var = 0b1011; 2.2 encode_tiresHáttér információ:A SA:MP-ban egy jármû abroncsainak adataid 4 bit tárolja. Az elsõ bit a jobb hátsó, a második bit a jobb elsõ, a harmadik bit a bal hátsó, a negyedik bit pedig a bal elsõ abroncsról tárol információt. Mivel 4 bitben eltárolhatóak az információk, így decimális formában 0-15 értékekkel is be lehet állítani a jármûvek abroncsait. 16-tól kezdve az értékek már ismétlõdni kezdenek, szóval a 16-nál az eredmény ugyanaz mint 0-nál, 17-nél ugyanaz mind 1-nél, és így tovább. Ez érthetõ, ha tudjuk hogyan épül fel a kettes számrendszer.(Ne felejtsük el, a biteket hátulról számozzuk!)Nos nézzük hogyan néz ki a kód: encode_tires(tire1, tire2, tire3, tire4){ return tire1 | tire2 << 1 | tire3 << 2 | tire4 << 3;} Láthatjuk, hogy bekérjük a 4 abroncs adatát(vagyis hogy ép[0], vagy kidurrant[1]), majd egy sima bitenkénti VAGY mûvelet és biteltolás kombinálásával visszatérünk az eredménnyel. A kódban a tire1 felel meg a jobb hátsó abroncsnak, a tire2 a jobb elsõ, a tire3 a bal hátsó, és a tire4 a bal elsõnek. Nézzünk egy konkrét példát. Tegyük fel, hogy én a hátsó 2 abroncsot ki akarom durrantani, még az elsõ kettõt épre akarom beállítani. Ez esetben így néz ki a dolog: encode_tire(1,0,1,0); Ha kiíratnánk konzolba a visszatérési értéket (%d-vel), 5-öt kapnánk. Miért is? Az 5 bináris alakja 0101. Hoppá, ha megnézzük ez pont fordítottja annak, amit mi megadtunk számjegyekként, csak már konkrét bináris számként. Nézzük hogyan is történhetett mindez, mit is csináltunk. A függvény a következõ mûveletet végzi: tire1 | tire2 << 1 | tire3 << 2 | tire4 << 3 Konyhanyelven úgy magyarázhatnánk el a dolgot, hogy ez a kód egymás mellé pakolgatja a megadott számokat, fordított sorrendben. Na de mi most nem a konyhanyelv miatt vagyunk ebben a leírásban, szóval nézzük a kódunkat. Lényegében ez futott le, a konkrét értékeinkkel nézve: 1 | 0 << 1 | 1 << 2 | 0 << 3 Bármily furcsa is, ez egy mûvelet, melyen szépen végig halad sorban a program. Elõször is írjuk át az egészet bináris alakba(az egyszerûség kedvéért most csak 4 bitet fogok kiírni): 0001 | 0000 << 1 | 0001 << 2 | 0000 << 3 Na erre se mondtuk volna rá a tutorial elsõ részének elolvasása elõtt, hogy ez egy mûveletsor. A rendszer elsõnek az eltolásokat végzi el (olyan ez mint a 2*3+2*4+2*5 mûveletben elõször a szorzásokat végezzük el, és csak utána adunk össze, ha nincs zárójel), és csak utána a bitenkénti VAGY mûveleteket, ezért mi is így fogunk tenni. Emlékezzünk, eltoláskor az \"üres\" bitek 0 értéket fognak felvenni. Végezzük el külön csak az eltolásokat: 0000 << 1 = 00000001 << 2 = 01000000 << 3 0000 Nos, ha ezeket elvégeztük, akkor nézzük, hogy is áll most a kódunk: 0001 | 0000 | 0100 | 0000 Négy azonos mûvelet van hátra, így hát a program innen más szépen sorban fog haladni. Tegyünk mi is így. Elsõ tehát a 0001 | 0000 mûvelet elvégzése. Emlékeztetõül, a bitenkénti VAGY mûvelet akkor ad 0-t eredményül, ha mind a kettõ érték 0, minden más esetben 1-et. Végezzük el külön az elsõ bitenkénti VAGY mûveletet: 0001 | 0000 = 0001 Nem volt bonyolult, nézzük tehát hogy áll akkor most a teljes mûveletünk: 0001 | 0100 | 0000 Haladunk szépen tovább, elvégezzük a 0001 | 0100 mûveletet. 0001 | 0100 = 0101 Nincs mit magyarázni rajta, egyszerû bitenkénti VAGY mûvelet volt ez is. Már csak ez van hátra: 0101 | 0000 Nos, ez sem lesz egy bonyolult mûvelet: 0101 | 0000 = 0101 Ezzel meg is kaptuk a végeredményt, ami nem más, mint a 0101 (tízes számrendszerben 5). Mivel azonban eléggé kusza lett így a számításunk, nézzük egyben hogyan is néz ki az egész, ha nem bontom ennyire részekre: 0100 | 0000 << 1 | 0001 << 2 | 0000 << 3v0001 | 0000 | 0100 | 0000v0001 | 0100 | 0000v0101 | 0000v0101 Nos, ezennel láthattuk a gyakorlatban is, hogyan mûködik a függvény. Kijelenthetjük tehát, hogy a függvény a számokat a sorrendjüknek megfelelõ számú bitbe helyezi. Mivel azonban a biteket hátulról számozzuk, így az 1,0,1,0 bemenetek végeredménye 0101 lesz. Így már nem is olyan bonyolult Na, ha már ilyen sokáig eljutottunk, akkor felhasználhatjuk ezt a kódot arra, hogy az alapjait figyelembe véve elkészítsük a decode változatot. Mivel 4 értékkel térünk vissza, ezért paraméterként kell majd 4 változó, melyeknek módosíthatjuk az értékét, illetve 1 paraméter pedig a dekódolandó szám. A függvényünk \"címsora\" tehát így néz ki: decode_tires(tires, &tire1, &tire2, &tire3, &tire4) Hogy példánk ne csak elmélet legyen, a tires változó legyen az elõbbi 0101 értékkel egyenlõ. Na akkor menjünk tovább. Bemeneti értékünk ugye a \"tires\" változóban lesz eltárolva, így ezzel fogjuk a mûveleteket végezni. Kezdjünk el gondolkozni. Hogyan is tudnánk ellenõrizni egy adatot. Legegyszerûbb ha a bitenkénti ÉS mûveletet alkalmazzuk. Miért is? Nos, a bitenkénti ÉS mûvelet ugye akkor ad eredményül 1-et, ha mind a 2 érték egy. Tehát ha min egy számot összehasonlítunk a 1-el(binárisan ugye 00000000000000000000000000000001, akkor az eredményünk lényegében arról fog tájékoztatni, hogy az elsõ bit 0, vagy 1. Nem is olyan bonyolult ez. Nézzük akkor az elsõ bitet. Arra emlékezhetünk még, hogy az elsõ bit a tire1, vagyis jobb hátsó abroncs adatait tárolja. Tehát: tire1 = tires & 1; Vagy konkrétan a példánk esetében: tire1 = 0101 & 0001 Mivel a bitenkénti ÉS mûvelet akkor ad 1-et eredményül, ha mind a kettõ bit 1, akkor már láthatjuk is, hogy miért elegendõ az elsõ bitet szemügyre venni. Mivel mind a 2 helyen az elsõ bit 1, ezért a tire1 változónk értéke is 1(0001) lesz. Egyszerû és nagyszerû, máris megkaptuk a jobb hátsó abroncs állapotát.Gondolkozzunk hát tovább. Nekünk most arra van szükségünk, hogy a 2. bit értékét megkapjuk. Hogyan kaphatjuk ezt meg? Legegyszerûbb megoldás, ha a tires változót jobbra shifteljük 1-el, majd ismételten bitenkénti ÉS mûveletet végzünk. Nézzük tehát, hogyan is nézne ez ki: tire2= tires >> 1 & 1; Vagy konkrétan a mi példánkban: tire2 = 0101 >> 1 & 0001vtire2 = 0010 & 0001 Ismételten bitenkénti ÉS mûvelet, és az eredményünk 0, mivel csak az egyik bit 1, a másik 0, így a bitenkénti ÉS mûvelet eredménye is 0. Ezen az elven megcsináljuk a maradék 2 abronccsal is: tire3= tires >> 2 & 1;tire4= tires >> 3 & 1; Vagy a mi esetünkben konkrét példánkkal: tire3 = 0101 >> 2 & 0001 // azaz: --> 0001 & 0001 = 1tire4 = 0101 >> 3 & 0001 // azaz: --> 0000 & 0001 = 0 A teljes kódunk tehát: decode_tires(tires, &tire1, &tire2, &tire3, &tire4){ tire1 = tires & 1; tire2 = tires >> 1 & 1; tire3 = tires >> 2 & 1; tire4 = tires >> 3 & 1;} A konkrét példánkban a 4 változónk értéke a függvény lefutása után: tire1: 1tire2: 0tire3: 1tire4: 0 Ha összehasonlítjuk, ezek pont azok az értékek, mint amiket az encode függvényben megadtunk, tehát úgy tûnik jól dolgoztunk 2.3 encode_lightsEz a rész lesz a legrövidebb. Az egész lámpás rész pontosan ugyanarra a sémára épül mint az abroncsok. Az adott bitek ugyanazon a \"helyen\" lévõ lámpa adatait szolgáltatják, ugyanúgy 0 az ép, és 1 a sérült lámpák állapot azonosítója, és ugyanúgy 1-1 bit tárolja az értékeket.Maga a kód: encode_lights(light1, light2, light3, light4){ return light1 | light2 << 1 | light3 << 2 | light4 << 3;} Láthatjuk hogy halál pontosan ugyanaz mint az encode_tires, csak más változónevekkel. Így hát a decode függvény is ugyan az lesz, csak a változóneveket kell módosítanunk: decode_lights(lights, &light1, &light2, &light3, &light4){ light1 = lights & 1; light2 = lights >> 1 & 1; light3 = lights >> 2 & 1; light4 = lights >> 3 & 1;} Ha megnézzük látjuk, hogy teljesen ugyan az a séma, ugyan az a felépítés, minden megegyezik, ezért nem is írtam le újból ugyanazt, amit egyszer már kifejtettem.2.4 encode_panelsHáttér információ:A SA:MP 7 panelrõl tárol adatokat. Egy panel adatait 4 bit írja le. Nos, ennél több infóra nekünk most nincs szükségünk, az elõzõ kód alapján ezen kód is megérthetõ, így az encode verzióhoz nem is nagyon szeretnék magyarázatot fûzni. A különbség az encode_tires-hez képest, hogy itt 4-essével shiftelünk, mivel 1 panel adatait 4 bit tárolja.Maga a kód: encode_panels(flp, frp, rlp, rrp, windshield, front_bumper, rear_bumper){ return flp | frp << 4 | rlp << 8 | rrp << 12 | windshield << 16 | front_bumper << 20 | rear_bumper << 24;} Nézzünk egy decode verziót, ehhez a kódhoz. Alapjaiban ugyanazon a vonalon kell elindulnunk, mint a decode_tires esetében. Itt most 7 visszatérési értékünk lesz, és 1 bemeneti érték. Tehát így néz ki a függvény header: decode_panels(panels, &flp, &frp, &rlp, &rrp, &windshield, &front_bumper, &rear_bumper) A folytatásban megint csak az elõbbi verziót kell alapul vennünk, azonban itt lesz egy kis különbség. Egyértelmû, hogy mivel 1 panel adatait 4 bit tárolja, így a 4 bitet nekünk egyben kell lekérni, és egyben kell vele visszatérnünk. Megint csak a bitenkénti ÉS mûveletet fogjuk alkalmazni, azonban mivel 4 bitrõl van szó, így egy olyan számmal kell összehasonlítanunk, mely bináris alakban 1111. Ez a szám nem más, mint a 15. Hogy miért ez kell?Ha egy bitsorozat tartalmát ki szeretnénk másolni egy másik változóba, akkor a bitenkénti ÉS mûveletre vna szükségünk. A bitenkénti ÉS mûvelet akkor ad 1-et eredményül, ha mindkét bit 1, ha valamelyik 0, akkor a visszatérés már 0 lesz. Ergó tehát, ha egy csupa 1-esekbõl álló bitsorozattal vetjük össze az eredeti bitsorozatunkat, akkor lényegében lemásoljuk a sorozatot, egy másik változóba, hisz eredményül mindig az eredeti sozatot fogjuk visszakapni. Íme néhány példa: 0101 & 1111 = 01011111 & 1111 = 11110000 & 1111 = 00001011 & 1111 = 1011 Láthatjuk tehát, hogy ha csupa 1-est tartalmazó bitsorozattal hasonlítjuk össze az eredeti számunkat, akkor lényegében egy másolatot készítünk. Mivel nekünk erre a 4 bitre van szükségünk a 32-bõl, így pontosan ezt kell tennünk, ezen 4 bitet lemásolni egy másik változóba, hogy késõbb ezt majd egy másik függvénnyel még jobban darabokra szedhessük.Íme tehát akkor a kódunk elsõ sora: flp = panels & 15; Mivel 4-esével vizsgáljuk a biteket, így lényegében annyit kell még változtatnunk az elõzõ kódon(decode_tires), hogy az 1-esével növekvõ shiftelések helyett 4-esével shifteljünk. Azaz a 2. panel adatainak lekérdezése: frp = panels >> 4 & 15; Innen pedig már csak mindig további 4 shiftelést el kell végeznünk, és szépen végig haladhatunk a panels változó bitjein, és megkaphatjuk minden panel adatait külön-külön. rlp = panels >> 8 & 15;rrp = panels >> 12 & 15;windshield = panels >> 16 & 15;front_bumper= panels >> 20 & 15;rear_bumper = panels >> 24 & 15; Ezennel minden panel adatait megkaptuk. Ezután már akár a decode_tires függvényt felhasználva szét lehet szedni az adatokat darabokra, de akár egy saját névvel, és más változó nevekkel ellátott másik függvényt is létre lehet hozni a decode_tires mintájára.Tehát az egész kód egyben: decode_panels(panels, &flp, &frp, &rlp, &rrp, &windshield, &front_bumper, &rear_bumper){ flp = panels & 15; frp = panels >> 4 & 15; rlp = panels >> 8 & 15; rrp = panels >> 12 & 15; windshield = panels >> 16 & 15; front_bumper= panels >> 20 & 15; rear_bumper = panels >> 24 & 15;} Igazából a függvény ugyanarra a sémára épül mint a decode_tires, csak annyi a különbség, hogy 4 bitenként kapjuk az adatokat, nem pedig egyesével, mivel itt minden panelnek 4 adatát tudjuk majd még lekérdezni. Nem tudom lehet-e még ehhez a részhez hozzáfûzni valamit, szóval szerintem ugorjunk. 2.5 encode_doorsHáttér információ:A SA:MP képes minden jármû 4 \"ajtajának\" állapotát tárolni. Ezek az \"ajtók\"(Doors-t hogy fordíthatnám jobban?) konkrétan a motorháztetõ, csomagtartó, vezetõoldali ajtó, és anyósülés oldali ajtó. A 2 hátsó ajtó állapotát jelenleg még nincs lehetõségünk lekérdezni.Amikor lekérdezzük egy jármû \"ajtajának\" adatait, azt a SA:MP 4 bájtként(32 bit) adja vissza. Ebbõl a 4 bájtból 1 bájt(8 bit) jut minden egyes objektumra. Vagyis minden egyes objektum adatait 8 bit tárolja. Vagyis tárolhatná...A SA:MP csak az elsõ 3 bitet használja, a további bitek üresek lesznek, nincs funkciójuk, talán majd késõbb lesz. Az elsõ bit azt tárolja, hogy az objektum zárt[0], vagy nyitott[1](nem az jelenti, hogy bezárt, hanem becsukott/kinyitott) állapotban van-e. A második bit azt adja meg, hogy az objektum ép[0] vagy sérült[1]. A 3. bit arról tájékoztat, hogy az adott objektum rajta van-e még a jármûvön[0] vagy már leesett[1]. A többi bit mint mondtam üres. Ez majd a decode függvénynél lesz fontos.Nézzük tehát a konkrét kódot: encode_doors(bonnet, boot, driver_door, passenger_door, behind_driver_door, behind_passenger_door){ #pragma unused behind_driver_door #pragma unused behind_passenger_door return bonnet | boot << 8 | driver_door << 16 | passenger_door << 24;} A 2 hátsó ajtóval nem foglalkozunk, mivel azok jelenleg még nem támogatottak ugyebár, de a kód készítõje gondolt a jövõre is, vagy fogalmam sincs miért adta meg ezt a két paramétert is, hisz ígyis-úgyis módosítani kell a kódon... Ha behoznak még 2 ajtót, akkor az 1 bájtos tárolással már 6 bájt lenne, de 1 változó maximum 4 bájt lehet, tehát a kódot úgyis módosítani kell, ha meg arra gondoltak, hogy legalább a paramétereket nem kell majd szegény scriptereknek minden sorban átírni, az megint hülyeség, hisz ki adna értéket olyan paraméternek, amely nincs használva?...Nos mindegy, nem a kritizálásért vagyunk most itt. Ha megnézzük, a kód 8-as shiftelést használ, mivel egy objektum adatait 8 bit tárolja. A mûködési elv ugyanaz, mint az elõzõ encode függvényeknél, így erre nem kívánok megint kitérni.Nézzük azonban hogyan írnánk ehhez egy decode függvényt. Mikor megkérdeztem errõl ismerõsömet, õ azt mondta, ugyanúgy mint az elõzõ, decode_panels függvénynél, csak 8-as shiftelést használunk, és 15 helyett 255-el végezzük a bitenkénti ÉS mûveletet. 255-el? minek?No erre is ki fogok térni késõbb, de haladjunk a szokásos módon. 1 bemeneti paraméterünk van, és 4 kimeneti, vagyis a header rész a következõ: decode_doors(doors, &bonnet, &boot, &driver_door, &passenger_door) Ezzel meg is vagyunk, itt nem látok különösebb magyarázni valót. Én most nem fogok törõdni a 2 nem létezõ ajtóval, ha egy jövõbeni verzióban netán azokat is behoznák mint lekérdezhetõ objektum, akkor ígyis-úgyis módosítani kell majd a kódot, addig meg úgysincs rá szükségünk.Nos, mint az eddigi decode függvényeknél, elsõ feladatunk egy shiftelés mentes bitenkénti ÉS mûvelet elvégzése. Az egyik megoldás a következõ: bonnet = doors & 255; Lehet így is. Azonban véleményem szerint felesleges itt egy ekkora számmal \"szenvednünk\". Miért használjunk ekkora számot, és ezáltal bonyolítsuk feleslegesen a kódunkat, ha nekünk a 8 bitbõl csak az elsõ 3 lesz ami adatot tárol? Bõven elegendõ egy olyan szám, amely bináris alakja 111. Ez nem más, mint a 7. Tehát a következõ megoldás ugyanúgy helyes: bonnet = doors & 7; A két kód pontosan ugyanazt az eredményt fogja visszaadni, semmi különbség a kettõ között. Csupán elsõ esetében nagy számmal dolgozunk, még második verzió esetén csak akkorával, amekkora szükséges.A kód többi részében csak 8-as shifteléssel végigcammogunk a doors változó többi bitjén is: boot = doors >> 8 & 7;driver_door = doors >> 16 & 7;passenger_door = doors >> 24 & 7; A teljes kód egyben: decode_doors(doors, &bonnet, &boot, &driver_door, &passenger_door){ bonnet = doors & 7; boot = doors >> 8 & 7; driver_door = doors >> 16 & 7; passenger_door = doors >> 24 & 7;} A kód pont ugyanazon az elven épül fel, mint az összes többi decode függvényünk, csak épp a benne használatos értékek az \"ajtó\" objectumok adatstruktúrájához vannak igazítva.2.6 Egyéb ötletek gyakorlati alkalmazásra, hasznosításraJoggal felmerülhet a kérdés, hogy ennyi? Emiatt rágtuk végig magunkat ezen néhány tízezer karakteren? Nem. Nem ennyi. A SA:MP-ban rengeteg felhasználási módja lehet még a bitmûveleteknek. Mutatok is néhány dolgot, mire jók még.2.6.1 RGBA->HEX; HEX->RGBA konvertálásokNos, bármily \"hihetetlen\", erre is használható. Miért is? Nos, 16-os számrendszerben 0-9, valamint A-F \"szánjegyek\" léteznek. A hexadecimális számjegy decimális értékben 0-15 lehet. Ha megnézzük bináris értékeket, 0-15 az 4 bittel leírható, hiszen a 15 az bináris alakban 1111. De ha józan paraszti ésszel belegondolunk is. 16 az 24. Így tehát 1 hexadecimális érték, az 4 bináris értékkel írható le. Ebbõl már sejthetõ, hogy itt is shiftelésre lesz szükségünk. Naná Mivel 4 bit ír le egy hexadecimális számjegyet, így 4-esével kell majd shiftelnünk.Mi is az az RGBA?RGBA: A színek olyan meghatározási módja, melyben a 3 alapszín([r]ed, [g]reen, lue) mellett egy 4. érték, az úgynevezett [a]lpha is szerepel, amely az áttetszõséget adja meg. A legtöbb program(pl. paint, PS), és pl. a css(külön függvénnyel!) is elfogadja ezen értékeket decimális számokként(0-255) is, a SA:MP-ban azonban hexadecimális alakban kell megadnunk. Bármily furcsa a következõ 4 sor pont ugyan azt jelenti:\"Normál\" RGB alak(pl. paint, photoshop): Piros: 0, Zöld: 255, Kék: 255CSS alak(függvénnyel): rgba(0, 255, 255, 1);Pawn alak(1): 0x00FFFFFFPawn alak(2): 16777215 Ó igen, a Pawn-ban decimális számokként is megadhatjuk a színeket, hisz a program mindent bináris számrendszerbe fog átszámolni. De akár bináris alakban is megadhattuk volna:Pawn alak(3): 0b111111111111111111111111 Az, hogy mi a Pawn-nak hexadecimális alakban szoktuk megadni, annak az egyetlen oka, hogy így a leginkább \"átlátható\", és ez egy amúgy is elfogadott módja (mármint a hexadecimális számmal való megadás) a színek leírásának a számítástechnikában.Nos, mivel 1 szín 0 és 255 közötti értéket tartalmaz a piros, zöld és kék színekbõl, ez hexadecimálisan 2 számjeggyel leírható: 00-FF. Az FF értéke 255, aki nem hiszi, számoljon utána (162-1)2 hexadecimális számjegy az pedig 8 bináris számjeggyel leírható(00000000-11111111). Így már láthatjuk, hogy 8-as shiftelésre lesz szükségünk. Másra nincs is szükségünk, az RGBA->HEX konverter elkészítéséhez.stock RGBAToHex( r, g, b, a ){return a | b << 8 | g << 16 | r << 24;} Az, hogy miért fordított sorrendben kellett megadnunk az értékeket, az megint csak azzal magyarázható, hogy a biteket hétköznapi értelemben véve visszafelé számozzuk.Egy ellentét, azaz HexToRGBA függvény megírása sem túl bonyolult, ugyan ezt kell csinálnunk, csak visszafelé.stock HexToRGBA( colour, &r, &g, &b, &a ){a = colour & 0xFF;b = (colour >> 8 ) & 0xFF;g = (colour >> 16 ) & 0xFF;r = (colour >> 24 ) & 0xFF;} A 0xFF-et természetesen decimális értékkel is megadhatjuk(255).No akkor lehet ugye RGBA-t ARGB-be is konvertálni, az így néz ki:RGBAToARGBHEX(r,g,b,a){return (r & 0xff) << 24 | (g & 0xff) << 16 | (b & 0xff) << 8 | a & 0xff;} Ez máris egy újabb felhasználási módja a bitmûveleteknek. De, mutatok még 2.6.2 Boolen változók helyett IntegerAz al-al-alcímet elolvasva joggal felmerülhet a kérdés, hogy ez meg hogy jön ide. Nos ez úgy jön ide, hogy most egy kis memóriatakarékosságról fogok itt regélni. Adhattam volna azt is címnek, hogy miért ne használjunk boolean változókat, ha legalább 5 igaz/hamis értéket el akarunk tárolni.Nos akkor egy kis memórialecke. Azt már tisztáztuk, hogy az integer változók 32 bitesek, vagyis 4 bájtot foglalnak le a memóriában maguknak. A boolean típusú változókról azonban még nem volt szó. Mivel 1 értéket tárolnak csak, az is csak igaz vagy hamis lehet, így joggal gondolhatnánk, hogy 1 bit a mérete. Azonban nem...1 boolean változó 8 bitet, azaz 1 bájtot foglal le magának a memóriában. Hogy ez miért így van, az a számítógépek memóriahasználatával magyarázható. Manapság nincs mód arra, hogy a memóriában egyetlen bitet módosítsunk, csak 8-as \"csomagokban\", vagyis bájtonként tudjuk ezt megtenni. Hogy ez miért van így, azt most nem fogom itt fejtegetni Nos, ha megnézzük tehát, a boolean változó egy nagyon-nagyon pazarló megoldás. Hiszen 1 darab boolean változó 1 bájtot foglal, 4 darab boolean változó esetén ez már 4 bájt, ami megegyezik 1 integer változó méretével. Azonban, még a 4 boolean változóban 4 igaz/hamis érték kerülhet tárolásra, addig egy integerben 32 igaz/hamis értéket is eltárolhatunk.Bizony, 32 igaz/hamis érték. Hiszen 1 integer változó 32 bitbõl áll, ha minden bitet hasznosítunk, akkor a bitmûveletek felhasználásával 32 igaz/hamis értéket is eltárolhatunk. Összehasonlítva:32 darab boolean változó mérete 32 bájt.1 integer változó mérete: 4 bájt. Vagyis 32 boolean változó esetén nyolcad annyi memóriánkba kerül, ha egy darab integerben tároljuk el az értékeket. Persze 32 booleanra ritkán van szükség, de egy RP modban simán elõfordulhat. Gondoljunk csak bele... Van 32 boolean-tömbös változónk, ez a maximum slotszámot(1000) figyelembe véve: 32*1000 byte azaz 32000 byte ami ~31 kilobyte. Ha ezt egyetlen integer-tömbbel oldanánk meg: 4*1000 byte, azaz 4000 byte, ami ~3,9 kilobyte. Azért így nézve már nem annyira \"elhanyagolandó\" az a különbség szerintem =)Akár írhatnánk erre külön egy függvényt, mely az adott bit értékével tér vissza. Nem is bonyolult. Így már azok számára is használhatóak a bitmûveletek, akik nem értenek hozzá, és rengeteg memóriát takaríthatnak meg.Íme egy függvény az adatok kiolvasására:stock GetData(integer, bit){ return integer >> bit & 1; //vagy az 1 helyett akár 0x01 alakban is megadhatjuk, nincs jelentõsége} És a párja, adat írásra:stock SetData(&integer, bit, value){ integer |= value << bit; return 1;} Persze ezt még lehetne csinosítani, hogy pl. ne 0-tól hanem 1-tõl számoljuk a biteket, vagy hogy 31(vagy 32 ha 1-tõl számolunk) felett 0 legyen a return értéke, esetleg a value-re is rakhatnánk ellenõrzést, hogy 0-1-e az értéke, vagy más, de ilyenekkel most nem törõdtem.2.6.3 Több érték, egy integerbeNa ez az a rész, ami véleményem szerint már csak a nagyon megrögzött memóriafelszabadítók szerint nem lesz felesleges Amennyiben nincs szükségünk túl nagy számokra(pl. csak 0-15, vagy akár csak 0-7, de akár még a 0-255 is megfelel), akkor akár egy integerbe is tehetjük õket. Hogyan? Egyszerû. Csoportosítjuk a biteket. Tegyük fel, hogy maradván egy RP szervernél, el akarjuk tárolni ûbergigantikusan memóriakímélõ megoldással a játékos házának számát(ez legyen mondjuk 0-100 közötti szám), a játékos frakcióját(ez legyen 0-15 között), a játékos rangját(0-7 között), hogy a telefonja be van-e kapcsolva(0-1), a játékos szolgálati jármûvének id-jét(legyen 0-255 között), és az alapmunkáját(0-15 között). Ez is megoldható, egyetlen integerben, és ismételten 1000 slottal számolva máris megspóroltunk 4*4*1000+1*1000 byte-t(5 integer, és 1 boolean helyett csupán 1 integert használunk), ami ~16,6 kilobyte. Azért nem mindegy az, hogy 20,5 kb-ot vagy 4 kb-ot használunk szerintem... Ezt a következõ módszerrel valósíthatnánk meg:#define DATA_HOUSE 0#define DATA_FACTION 1#define DATA_RANK 2#define DATA_PHONESTATE 3#define DATA_DUTYCAR 4#define DATA_JOB 5stock GetData(playerinfo, data){ switch(data) { case DATA_HOUSE: return playerinfo & 127;//7 bit case DATA_FACTION: return playerinfo >> 7 & 15;//4 bit case DATA_RANK: return playerinfo >> 11 & 7;//3 bit case DATA_PHONESTATE: return playerinfo >> 14 & 1;//1 bit case DATA_DUTYCAR: return playerinfo >> 15 & 255;//8 bit case DATA_JOB: return playerinfo >> 23 & 15;//4 bit}return -1;}stock SetData(&playerinfo, data, value){ switch(data) { case DATA_HOUSE: { playerinfo = playerinfo & ~127 | value; }//7 bit (0-127 közötti maximálisan) case DATA_FACTION: { playerinfo = playerinfo & ~(15 << 7) | value << 7; }//4 bit (0-15 között) case DATA_RANK: { playerinfo = playerinfo & ~(7 << 11) | value << 11; }//3 bit (0-7 között) case DATA_PHONESTATE: { playerinfo = playerinfo & ~(1 << 14) | value << 14; }//1 bit (0-1) case DATA_DUTYCAR: { playerinfo = playerinfo & ~(255 << 15) | value << 15; }//8 bit (0-255 között) case DATA_JOB: { playerinfo = playerinfo & ~(15 << 23) | value << 23; }//4 bit (0-15 között) } return -1;}stock SetDatas(&playerinfo, house, faction, rank, phonestate, dutycar, job){playerinfo |= house | (faction << 7) | (rank << 11) | (phonestate << 14) | (dutycar << 15) | (job << 23);} Ez már azért elég morbid, ezt inkább csak érdekességképp mutattam, de éppenséggel ez is használható lenne, csak ehhez már kell egy kis õrültség 2.7 Második fejezet végeNos, láthattuk hogyan is mûködik mindazon sok \"marhaság\", amit az elsõ fejezetben elméletként átvettünk. Láthattuk hogyan mûködnek a jármûvek paneljének adataival babráló encode függvények, valamint megmutattam, hogyan írhatunk hozzájuk decode verziókat. Láthattunk még példát egyéb gyakorlati hasznosításra, kicsit megkoccoltuk a hexadecimális számok világát is, majd szóba került egy kis memóriatakarékosság. Végül mutattam egy összetettebb, elvetemült példát is, a bitmûveletek hasznosítására.3. Összegzés, végszó Úgy gondolom, hogy elég részletesen körbe jártuk a témát. Megnéztük a bitmûveletek elméleti, és gyakorlati oldalát is, láthattunk gyakorlati példákat. Nem kell elsõre megérteni ezt az egészet, sõt... Elegendõ ha egyszer elolvassuk, emésztjük kicsit, majd késõbb megint elõvesszük, akkor netán már kicsit többet is megértünk belõle. Nem egy kihagyhatatlan része a Pawno-nak, de azért van annak haszna, ha az ember jártas ebben a témában is, bár sokak szerint nem éri meg a sok fáradtságot, és idõt, mely a megértéséhez, begyakorlásához, használatának elsajátításához szükséges. Próbáltam mindent részletesen kifejteni. A bonyolultabb dolgokhoz sokat magyarázni, az egyszerûbbek esetén csak visszautalni az alapokhoz. Sok dolgot talán már túl is magyaráztam. Ezt a leírást nem 1 nap alatt készítettem el, kb. 1 hónapja írtam meg az elméleti oldalát, most az elmúlt pár napban pedig a gyakorlatot. Esténként 1-2 órát szántam rá csupán, és mivel ilyenkor már nem vagyok épp a toppon, elképzelhetõ, hogy néhány helyen helyesírási, vagy elvi hibát vétettem. Amennyiben valaki ilyet találna, kérem szóljon, és korrigálni fogom. Nos, azt hiszem nincs is más hátra, mint hogy sok szerencsét kívánjak a bitmûveletek elsajátításához, és megosszam a forrásokat. Remélem néhányan hasznosnak találják, eme igen hosszúra sikerült leírást.4. Források: Internet:WikipédiaSA:MP WikiSA:MP hivatalos fórumKönyv, e-book:Pohl László - A programozás alapjai(BME - Elektronikus Eszközök Tanszéke, Budapest, 2010)Siroki László - C programozás - Bitmûveletek jegyzeteˆ Anthony, 2012 - Minden jog fenntartva.A leírás egészben, vagy részleteiben szabadon terjeszthetõ, ameddig egyértelmûen fel van tüntetve az eredeti készítõ, valamint hogy honnan is származik. « Utoljára szerkesztve: 2012. augusztus 01. - 10:53:54 írta Anthony » Naplózva
00000000000000000000000000000011 & 00000000000000000000000000000101 = 0000000000000000000000000000000100000000000000000000000001110110 & 00000000000000000000000001011001 = 00000000000000000000000001010000[/quote]1.4.2 Bitenkénti (bitek közötti) VAGY mûveletA bitenkénti VAGY mûvelet (OR, |) egy logikai operátor. Fontos, hogy nem összekeverendõ a \"||\" logikai operátorral! Hasonlít rá, de ez a mûveleteket bitenként végzi. Ez az operátor akkor ad eredményül 1-et, ha legalább az egyik bit 1. Ha egyik bit sem 1, akkor 0-t ad eredményül, ha mindkét bit 1, akkor is 1 az eredmény. Igazság táblázata: A B A|B000101011111[/quote]Példának nézzük meg, mi történik, ha a bitenkénti VAGY operátort alkalmazzuk, szintén a 11 és a 7 esetén. A 11 bináris számrendszerben mint már írtam 00000000000000000000000000001011-nek felel meg, a 7 pedig 00000000000000000000000000000111-nek. Tehát a 11 | 7, az ugyaz mint ha ezt írnám: 00000000000000000000000000001011 | 00000000000000000000000000000111. Az átláthatóság kedvéért ismét egymás alá fogom írni a 2 számot, hogy áttekinthetõbb legyen, és az eredményt pedig a 3. sorba: 00000000000000000000000000001011|0000000000000000000000000000011100000000000000000000000000001111[/quote]Eredményül tehát 00000000000000000000000000001111-et kaptunk, ami a kettes számrendszerben a 15-nekk felel meg. A következõ 2 sor tehát ugyanazt jelenti: Bin:00000000000000000000000000001011|00000000000000000000000000000111=00000000000000000000000000001111Dec:11|7=15[/quote]Megint néhány példa: 00000000000000000000000000000011 | 00000000000000000000000000000101 = 0000000000000000000000000000011100000000000000000000000001110110 | 00000000000000000000000001011001 = 00000000000000000000000001111111[/quote]1.4.3 Bitenkénti (bitek közötti) KIZÁRÓ VAGY mûveletA bitenkénti KIZÁRÓ VAGY mûvelet (XOR, ^) egy logikai operátor. Ez az operátor nagyon hasonlít a VAGY operátorra, az a fõ különbség a kettõ között, hogy amennyiben mindkét bit 1-es, akkor a KIZÁRÓ VAGY operátor 0-t ad eredményül, a többi esetben ugyanúgy viselkedik mint a VAGY operátor. Igazság táblázata: A B A^B000101011110[/quote]Példának nézzük meg, mi történik, ha a bitenkénti KIZÁRÓ VAGY operátort alkalmazzuk, szintén a 11 és a 7 esetén. A 11 bináris számrendszerben mint már írtam 00000000000000000000000000001011-nek felel meg, a 7 pedig 00000000000000000000000000000111-nek. Tehát a 11 ^ 7, az ugyaz mint ha ezt írnám: 00000000000000000000000000001011 ^ 00000000000000000000000000000111. Az átláthatóság kedvéért ismét egymás alá fogom írni a 2 számot, hogy áttekinthetõbb legyen, és az eredményt pedig a 3. sorba: 00000000000000000000000000001011^0000000000000000000000000000011100000000000000000000000000001100[/quote]Eredményül most 00000000000000000000000000001100-t kaptunk, ami a kettes számrendszerben a 12-nek felel meg. Érdekes, hogy teljesen más eredményt kapunk, pedig lényegében ugyanazt az eredményt adja az operátor, - kis különbséggel ugyebár - mint a VAGY operátor.A következõ 2 sor tehát ugyanazt jelenti: Bin:00000000000000000000000000001011^00000000000000000000000000000111=00000000000000000000000000001100Dec:11^7=12[/quote]Megint néhány példa: 00000000000000000000000000000011 ^ 00000000000000000000000000000101 = 0000000000000000000000000000011000000000000000000000000001110110 ^ 00000000000000000000000001011001 = 00000000000000000000000000101111[/quote]Ezzel kapcsolatban egy érdekességet mutatnék meg. Vegyünk 2 számot. A látványosság kedvéért én példának a 118-at és a 89-et veszem. 1. szám: 00000000000000000000000001110110 = 1182. szám: 00000000000000000000000001011001 = 89Mûvelet elvégzése: 00000000000000000000000001110110 ^ 00000000000000000000000001011001 = 00000000000000000000000000101111 = 471. szám az eredmény, 2. szám az eredeti 2. szám: 00000000000000000000000001011001 = 89Eredmény: 00000000000000000000000000101111 ^ 00000000000000000000000001011001 = 00000000000000000000000001110110 = 118[/quote]Vagyis visszakaptuk a kiindulási állapotot 1.4.4 Bitenkénti (bitek közötti) NEGÁLÁSA bitenkénti NEGÁLÁS (NOT, ~) az utolsó logikai operátor, melyet meg fogok mutatni. Ez az operátor eléggé eltér az eddigiektõl, leginkább abban, hogy ezt a mûveletet nem 2 bitre alkalmazzuk, hanem csak 1-re. Szokás komplementer-operátornak is hívni(konyhanyelven). Lényegében az 1-esekbõl 0-t, a 0-kból 1-est csinál. Igazság táblázata: A ~A0110[/quote]Példának nézzük meg, mi történik, ha a bitenkénti NEGÁLÁST alkalmazzuk a 7-es számra. A 7 bináris számrendszerben mint már írtam 00000000000000000000000000000111-nek felel meg. Tehát a ~7, az ugyaz mint ha ezt írnám: ~00000000000000000000000000000111. Nézzük tehát, mi történik: ~0000000000000000000000000000011111111111111111111111111111111000[/quote]Eredményül most 11111111111111111111111111111000-t kaptunk, ami a kettes számrendszerben a -8-nak felel meg. A következõ 2 sor tehát ugyanazt jelenti: Bin:~00000000000000000000000000000111=11111111111111111111111111111000Dec:~7=-8[/quote]Megint néhány példa: ~00000000000000000000000000000101 = 11111111111111111111111111111011~00000000000000000000000001110110= 11111111111111111111111110001010[/quote]Ehhez kapcsolódóan lenne itt egy fontos megjegyzendõ dolog. Aki nem érti, hogy miért -8 a megoldás, az nem olvasta el elég rendesen a \"Részletesebben a bitekrõl a Pawn-ban\" részt.1.4.5 Biteltolás (arithmetic shift, shiftelés) operátor(ok)A shift operátorok(<<, >>) a bitek tologatására használhatóak. A << balra tol, a >> jobbra. Bal oldalt található a szám, amit tologatni kívánunk, jobb oldalt pedig az, hogy mennyivel szeretnénk eltolni.1.4.5.1 Jobbra toló operátor(>>):Ez az operátor a szám bitjeit jobbra tolja a megadott mértékkel. Egy bittel való eltolás, 2-vel való osztásnak felel meg. Nézzünk egy példát: 00000000000000000000000000001000//8>>200000000000000000000000000000010//2[/quote]Láthatjuk, hogy semmi más nem történt, mint minden bitet 2-vel hátrébb mozgattunk. Az így \"megüresedett\" bitek helyére pedig 0-k kerültek. Lényegében az történt, hogy az utolsó 2 bit kiesett, az így megmaradt biteket 2-vel hátrébb csúsztattuk, majd az elsõ 2, így \"üres\" bit 0 értéket kapott.Nézzünk egy példát negatív számmal: 11111111111111111111111111110000//-8>>211111111111111111111111111111100//-2[/quote]Láthatjuk, hogy itt fontos szerepe van az MSB-nek. Ettõl függ ugyanis, hogy 0-k vagy 1-esek kerülnek a \"megüresedett\" bitek helyére. Fontos tehát megjegyezni, hogy a jobbra shiftelés megtartja a számok elõjelét, nem változtat azon!1.4.5.2 Balra toló operátor(<<):Ez az operátor a szám bitjeit balra tolja a megadott mértékkel. Egy bittel való eltolás, 2-vel való szorzásnak felel meg. Nézzünk egy példát: 00000000000000000000000000001000//8<<200000000000000000000000000100000//32[/quote]Itt megint könnyedén észrevehetõ mi is történt. Minden bitet fentebb \"toltunk\" 2-vel. Azok a bitek, amelyek ezáltal a 32. bit elé kerültek, kiesnek. Az így megüresedett bitek 0-kkal egészültek ki. Eddig nem túl bonyolult. Azonban figyeljünk oda, ha túl sokáig shiftelünk egy pozitív számot, akkor az utolsó érték, melyet fel tud venni a változónk (mielõtt még túlcsordulás miatt elérné a 0-t), az egy negatív szám!Nézzük mi történik negatív számok esetén: 11111111111111111111111111111000//-8<<211111111111111111111111111100000//-32[/quote]Láthatjuk hogy a balra shifteléskor a szám mindig 0-kkal egészítõdik ki, legyen az akár negatív, akár pozitív. Nem veszi figyelembe az MSB értékét, így ha túl sokat shiftelünk, akkor elõfordulhat, hogy a szám elveszti negatív értékét, és eléri a 0-t.1.4.6 Logikai biteltolás (logical shift, logikai shiftelés) operátorElõször is le kell tisztázni, hogy logikai shiftelésbõl csak egy létezik, jobbra logikai shiftelés(>>>). Sokban hasonlít a jobbra shiftelõ operátorra (>>), azonban van egy fontos különbség.Nézzük az elsõ példát: 00000000000000000000000000001000//8>>>200000000000000000000000000000010//2[/quote]Eddig nem igazán tapasztalhattunk semmi változást, minden pont ugyanúgy zajlott, mint a jobbra shiftelésnél(>>).Nézzük mi történik negatív szám esetén: 11111111111111111111111111111000//-8>>>200111111111111111111111111111110 //1073741822[/quote]Itt már láthatjuk a különbséget. Logikai shiftelés esetén az üres bitek helyére bekerülõ érték nem függ az MSB-tõl, hanem mindig 0. Emiatt soha nem kaphatunk eredményül negatív számot, logikai shiftelés esetén.1.5 Elsõ fejezet végeNos, ezennel átvettünk mindent, amit érdemes tudni a bitmûveletekrõl. Megmutattam az alapok, hogy hogyan épülnek fel a változók, valamint átvettük a fõ operátorokat. Eddig csak színtiszta elmélet volt. A következõ részben megmutatom, hogyan hasznosíthatjuk mindezt a gyakorlatban, vagyis mire is jó az a sok marhaság, amit eddig leírtam.2. Gyakorlati példák, érdekességek 2.1 Áttekintés, mikrõl is lesz még szóAz alábbiakban még pár dolgot szeretnék mutatni. Elõször is néhány \"függvénnyel\" fogok foglalkozni, majd mutatok még egy-két érdekességet. Konkrétan megmutatom, hogyan tudjuk egy jármû minden \"paneljének\" adatát kiíratni, részletesen(erre alapból nincs lehetõség, és a függvények is mind \"encode\" függvények, én viszont mutatok egy-egy decode \"verziót\" is.)Az alábbi 4 függvénnyel fogok foglalkozni: encode_tiresencode_panelsencode_doorsencode_lightsDe elõbb egy kis háttérinfó:A Pawnban a forrásban megadhatunk bináris számokat ugyanúgy, ahogyan Hex számokat is megadhatunk. Ehhez a \"0b\" prefixet kell használnunk. Tehát ha bináris számunk az 1011 és ezt akarjuk adni értékül egy változónak, azt így tehetjük meg: new var = 0b1011; 2.2 encode_tiresHáttér információ:A SA:MP-ban egy jármû abroncsainak adataid 4 bit tárolja. Az elsõ bit a jobb hátsó, a második bit a jobb elsõ, a harmadik bit a bal hátsó, a negyedik bit pedig a bal elsõ abroncsról tárol információt. Mivel 4 bitben eltárolhatóak az információk, így decimális formában 0-15 értékekkel is be lehet állítani a jármûvek abroncsait. 16-tól kezdve az értékek már ismétlõdni kezdenek, szóval a 16-nál az eredmény ugyanaz mint 0-nál, 17-nél ugyanaz mind 1-nél, és így tovább. Ez érthetõ, ha tudjuk hogyan épül fel a kettes számrendszer.(Ne felejtsük el, a biteket hátulról számozzuk!)Nos nézzük hogyan néz ki a kód: encode_tires(tire1, tire2, tire3, tire4){ return tire1 | tire2 << 1 | tire3 << 2 | tire4 << 3;} Láthatjuk, hogy bekérjük a 4 abroncs adatát(vagyis hogy ép[0], vagy kidurrant[1]), majd egy sima bitenkénti VAGY mûvelet és biteltolás kombinálásával visszatérünk az eredménnyel. A kódban a tire1 felel meg a jobb hátsó abroncsnak, a tire2 a jobb elsõ, a tire3 a bal hátsó, és a tire4 a bal elsõnek. Nézzünk egy konkrét példát. Tegyük fel, hogy én a hátsó 2 abroncsot ki akarom durrantani, még az elsõ kettõt épre akarom beállítani. Ez esetben így néz ki a dolog: encode_tire(1,0,1,0); Ha kiíratnánk konzolba a visszatérési értéket (%d-vel), 5-öt kapnánk. Miért is? Az 5 bináris alakja 0101. Hoppá, ha megnézzük ez pont fordítottja annak, amit mi megadtunk számjegyekként, csak már konkrét bináris számként. Nézzük hogyan is történhetett mindez, mit is csináltunk. A függvény a következõ mûveletet végzi: tire1 | tire2 << 1 | tire3 << 2 | tire4 << 3 Konyhanyelven úgy magyarázhatnánk el a dolgot, hogy ez a kód egymás mellé pakolgatja a megadott számokat, fordított sorrendben. Na de mi most nem a konyhanyelv miatt vagyunk ebben a leírásban, szóval nézzük a kódunkat. Lényegében ez futott le, a konkrét értékeinkkel nézve: 1 | 0 << 1 | 1 << 2 | 0 << 3 Bármily furcsa is, ez egy mûvelet, melyen szépen végig halad sorban a program. Elõször is írjuk át az egészet bináris alakba(az egyszerûség kedvéért most csak 4 bitet fogok kiírni): 0001 | 0000 << 1 | 0001 << 2 | 0000 << 3 Na erre se mondtuk volna rá a tutorial elsõ részének elolvasása elõtt, hogy ez egy mûveletsor. A rendszer elsõnek az eltolásokat végzi el (olyan ez mint a 2*3+2*4+2*5 mûveletben elõször a szorzásokat végezzük el, és csak utána adunk össze, ha nincs zárójel), és csak utána a bitenkénti VAGY mûveleteket, ezért mi is így fogunk tenni. Emlékezzünk, eltoláskor az \"üres\" bitek 0 értéket fognak felvenni. Végezzük el külön csak az eltolásokat: 0000 << 1 = 00000001 << 2 = 01000000 << 3 0000 Nos, ha ezeket elvégeztük, akkor nézzük, hogy is áll most a kódunk: 0001 | 0000 | 0100 | 0000 Négy azonos mûvelet van hátra, így hát a program innen más szépen sorban fog haladni. Tegyünk mi is így. Elsõ tehát a 0001 | 0000 mûvelet elvégzése. Emlékeztetõül, a bitenkénti VAGY mûvelet akkor ad 0-t eredményül, ha mind a kettõ érték 0, minden más esetben 1-et. Végezzük el külön az elsõ bitenkénti VAGY mûveletet: 0001 | 0000 = 0001 Nem volt bonyolult, nézzük tehát hogy áll akkor most a teljes mûveletünk: 0001 | 0100 | 0000 Haladunk szépen tovább, elvégezzük a 0001 | 0100 mûveletet. 0001 | 0100 = 0101 Nincs mit magyarázni rajta, egyszerû bitenkénti VAGY mûvelet volt ez is. Már csak ez van hátra: 0101 | 0000 Nos, ez sem lesz egy bonyolult mûvelet: 0101 | 0000 = 0101 Ezzel meg is kaptuk a végeredményt, ami nem más, mint a 0101 (tízes számrendszerben 5). Mivel azonban eléggé kusza lett így a számításunk, nézzük egyben hogyan is néz ki az egész, ha nem bontom ennyire részekre: 0100 | 0000 << 1 | 0001 << 2 | 0000 << 3v0001 | 0000 | 0100 | 0000v0001 | 0100 | 0000v0101 | 0000v0101 Nos, ezennel láthattuk a gyakorlatban is, hogyan mûködik a függvény. Kijelenthetjük tehát, hogy a függvény a számokat a sorrendjüknek megfelelõ számú bitbe helyezi. Mivel azonban a biteket hátulról számozzuk, így az 1,0,1,0 bemenetek végeredménye 0101 lesz. Így már nem is olyan bonyolult Na, ha már ilyen sokáig eljutottunk, akkor felhasználhatjuk ezt a kódot arra, hogy az alapjait figyelembe véve elkészítsük a decode változatot. Mivel 4 értékkel térünk vissza, ezért paraméterként kell majd 4 változó, melyeknek módosíthatjuk az értékét, illetve 1 paraméter pedig a dekódolandó szám. A függvényünk \"címsora\" tehát így néz ki: decode_tires(tires, &tire1, &tire2, &tire3, &tire4) Hogy példánk ne csak elmélet legyen, a tires változó legyen az elõbbi 0101 értékkel egyenlõ. Na akkor menjünk tovább. Bemeneti értékünk ugye a \"tires\" változóban lesz eltárolva, így ezzel fogjuk a mûveleteket végezni. Kezdjünk el gondolkozni. Hogyan is tudnánk ellenõrizni egy adatot. Legegyszerûbb ha a bitenkénti ÉS mûveletet alkalmazzuk. Miért is? Nos, a bitenkénti ÉS mûvelet ugye akkor ad eredményül 1-et, ha mind a 2 érték egy. Tehát ha min egy számot összehasonlítunk a 1-el(binárisan ugye 00000000000000000000000000000001, akkor az eredményünk lényegében arról fog tájékoztatni, hogy az elsõ bit 0, vagy 1. Nem is olyan bonyolult ez. Nézzük akkor az elsõ bitet. Arra emlékezhetünk még, hogy az elsõ bit a tire1, vagyis jobb hátsó abroncs adatait tárolja. Tehát: tire1 = tires & 1; Vagy konkrétan a példánk esetében: tire1 = 0101 & 0001 Mivel a bitenkénti ÉS mûvelet akkor ad 1-et eredményül, ha mind a kettõ bit 1, akkor már láthatjuk is, hogy miért elegendõ az elsõ bitet szemügyre venni. Mivel mind a 2 helyen az elsõ bit 1, ezért a tire1 változónk értéke is 1(0001) lesz. Egyszerû és nagyszerû, máris megkaptuk a jobb hátsó abroncs állapotát.Gondolkozzunk hát tovább. Nekünk most arra van szükségünk, hogy a 2. bit értékét megkapjuk. Hogyan kaphatjuk ezt meg? Legegyszerûbb megoldás, ha a tires változót jobbra shifteljük 1-el, majd ismételten bitenkénti ÉS mûveletet végzünk. Nézzük tehát, hogyan is nézne ez ki: tire2= tires >> 1 & 1; Vagy konkrétan a mi példánkban: tire2 = 0101 >> 1 & 0001vtire2 = 0010 & 0001 Ismételten bitenkénti ÉS mûvelet, és az eredményünk 0, mivel csak az egyik bit 1, a másik 0, így a bitenkénti ÉS mûvelet eredménye is 0. Ezen az elven megcsináljuk a maradék 2 abronccsal is: tire3= tires >> 2 & 1;tire4= tires >> 3 & 1; Vagy a mi esetünkben konkrét példánkkal: tire3 = 0101 >> 2 & 0001 // azaz: --> 0001 & 0001 = 1tire4 = 0101 >> 3 & 0001 // azaz: --> 0000 & 0001 = 0 A teljes kódunk tehát: decode_tires(tires, &tire1, &tire2, &tire3, &tire4){ tire1 = tires & 1; tire2 = tires >> 1 & 1; tire3 = tires >> 2 & 1; tire4 = tires >> 3 & 1;} A konkrét példánkban a 4 változónk értéke a függvény lefutása után: tire1: 1tire2: 0tire3: 1tire4: 0 Ha összehasonlítjuk, ezek pont azok az értékek, mint amiket az encode függvényben megadtunk, tehát úgy tûnik jól dolgoztunk 2.3 encode_lightsEz a rész lesz a legrövidebb. Az egész lámpás rész pontosan ugyanarra a sémára épül mint az abroncsok. Az adott bitek ugyanazon a \"helyen\" lévõ lámpa adatait szolgáltatják, ugyanúgy 0 az ép, és 1 a sérült lámpák állapot azonosítója, és ugyanúgy 1-1 bit tárolja az értékeket.Maga a kód: encode_lights(light1, light2, light3, light4){ return light1 | light2 << 1 | light3 << 2 | light4 << 3;} Láthatjuk hogy halál pontosan ugyanaz mint az encode_tires, csak más változónevekkel. Így hát a decode függvény is ugyan az lesz, csak a változóneveket kell módosítanunk: decode_lights(lights, &light1, &light2, &light3, &light4){ light1 = lights & 1; light2 = lights >> 1 & 1; light3 = lights >> 2 & 1; light4 = lights >> 3 & 1;} Ha megnézzük látjuk, hogy teljesen ugyan az a séma, ugyan az a felépítés, minden megegyezik, ezért nem is írtam le újból ugyanazt, amit egyszer már kifejtettem.2.4 encode_panelsHáttér információ:A SA:MP 7 panelrõl tárol adatokat. Egy panel adatait 4 bit írja le. Nos, ennél több infóra nekünk most nincs szükségünk, az elõzõ kód alapján ezen kód is megérthetõ, így az encode verzióhoz nem is nagyon szeretnék magyarázatot fûzni. A különbség az encode_tires-hez képest, hogy itt 4-essével shiftelünk, mivel 1 panel adatait 4 bit tárolja.Maga a kód: encode_panels(flp, frp, rlp, rrp, windshield, front_bumper, rear_bumper){ return flp | frp << 4 | rlp << 8 | rrp << 12 | windshield << 16 | front_bumper << 20 | rear_bumper << 24;} Nézzünk egy decode verziót, ehhez a kódhoz. Alapjaiban ugyanazon a vonalon kell elindulnunk, mint a decode_tires esetében. Itt most 7 visszatérési értékünk lesz, és 1 bemeneti érték. Tehát így néz ki a függvény header: decode_panels(panels, &flp, &frp, &rlp, &rrp, &windshield, &front_bumper, &rear_bumper) A folytatásban megint csak az elõbbi verziót kell alapul vennünk, azonban itt lesz egy kis különbség. Egyértelmû, hogy mivel 1 panel adatait 4 bit tárolja, így a 4 bitet nekünk egyben kell lekérni, és egyben kell vele visszatérnünk. Megint csak a bitenkénti ÉS mûveletet fogjuk alkalmazni, azonban mivel 4 bitrõl van szó, így egy olyan számmal kell összehasonlítanunk, mely bináris alakban 1111. Ez a szám nem más, mint a 15. Hogy miért ez kell?Ha egy bitsorozat tartalmát ki szeretnénk másolni egy másik változóba, akkor a bitenkénti ÉS mûveletre vna szükségünk. A bitenkénti ÉS mûvelet akkor ad 1-et eredményül, ha mindkét bit 1, ha valamelyik 0, akkor a visszatérés már 0 lesz. Ergó tehát, ha egy csupa 1-esekbõl álló bitsorozattal vetjük össze az eredeti bitsorozatunkat, akkor lényegében lemásoljuk a sorozatot, egy másik változóba, hisz eredményül mindig az eredeti sozatot fogjuk visszakapni. Íme néhány példa: 0101 & 1111 = 01011111 & 1111 = 11110000 & 1111 = 00001011 & 1111 = 1011 Láthatjuk tehát, hogy ha csupa 1-est tartalmazó bitsorozattal hasonlítjuk össze az eredeti számunkat, akkor lényegében egy másolatot készítünk. Mivel nekünk erre a 4 bitre van szükségünk a 32-bõl, így pontosan ezt kell tennünk, ezen 4 bitet lemásolni egy másik változóba, hogy késõbb ezt majd egy másik függvénnyel még jobban darabokra szedhessük.Íme tehát akkor a kódunk elsõ sora: flp = panels & 15; Mivel 4-esével vizsgáljuk a biteket, így lényegében annyit kell még változtatnunk az elõzõ kódon(decode_tires), hogy az 1-esével növekvõ shiftelések helyett 4-esével shifteljünk. Azaz a 2. panel adatainak lekérdezése: frp = panels >> 4 & 15; Innen pedig már csak mindig további 4 shiftelést el kell végeznünk, és szépen végig haladhatunk a panels változó bitjein, és megkaphatjuk minden panel adatait külön-külön. rlp = panels >> 8 & 15;rrp = panels >> 12 & 15;windshield = panels >> 16 & 15;front_bumper= panels >> 20 & 15;rear_bumper = panels >> 24 & 15; Ezennel minden panel adatait megkaptuk. Ezután már akár a decode_tires függvényt felhasználva szét lehet szedni az adatokat darabokra, de akár egy saját névvel, és más változó nevekkel ellátott másik függvényt is létre lehet hozni a decode_tires mintájára.Tehát az egész kód egyben: decode_panels(panels, &flp, &frp, &rlp, &rrp, &windshield, &front_bumper, &rear_bumper){ flp = panels & 15; frp = panels >> 4 & 15; rlp = panels >> 8 & 15; rrp = panels >> 12 & 15; windshield = panels >> 16 & 15; front_bumper= panels >> 20 & 15; rear_bumper = panels >> 24 & 15;} Igazából a függvény ugyanarra a sémára épül mint a decode_tires, csak annyi a különbség, hogy 4 bitenként kapjuk az adatokat, nem pedig egyesével, mivel itt minden panelnek 4 adatát tudjuk majd még lekérdezni. Nem tudom lehet-e még ehhez a részhez hozzáfûzni valamit, szóval szerintem ugorjunk. 2.5 encode_doorsHáttér információ:A SA:MP képes minden jármû 4 \"ajtajának\" állapotát tárolni. Ezek az \"ajtók\"(Doors-t hogy fordíthatnám jobban?) konkrétan a motorháztetõ, csomagtartó, vezetõoldali ajtó, és anyósülés oldali ajtó. A 2 hátsó ajtó állapotát jelenleg még nincs lehetõségünk lekérdezni.Amikor lekérdezzük egy jármû \"ajtajának\" adatait, azt a SA:MP 4 bájtként(32 bit) adja vissza. Ebbõl a 4 bájtból 1 bájt(8 bit) jut minden egyes objektumra. Vagyis minden egyes objektum adatait 8 bit tárolja. Vagyis tárolhatná...A SA:MP csak az elsõ 3 bitet használja, a további bitek üresek lesznek, nincs funkciójuk, talán majd késõbb lesz. Az elsõ bit azt tárolja, hogy az objektum zárt[0], vagy nyitott[1](nem az jelenti, hogy bezárt, hanem becsukott/kinyitott) állapotban van-e. A második bit azt adja meg, hogy az objektum ép[0] vagy sérült[1]. A 3. bit arról tájékoztat, hogy az adott objektum rajta van-e még a jármûvön[0] vagy már leesett[1]. A többi bit mint mondtam üres. Ez majd a decode függvénynél lesz fontos.Nézzük tehát a konkrét kódot: encode_doors(bonnet, boot, driver_door, passenger_door, behind_driver_door, behind_passenger_door){ #pragma unused behind_driver_door #pragma unused behind_passenger_door return bonnet | boot << 8 | driver_door << 16 | passenger_door << 24;} A 2 hátsó ajtóval nem foglalkozunk, mivel azok jelenleg még nem támogatottak ugyebár, de a kód készítõje gondolt a jövõre is, vagy fogalmam sincs miért adta meg ezt a két paramétert is, hisz ígyis-úgyis módosítani kell a kódon... Ha behoznak még 2 ajtót, akkor az 1 bájtos tárolással már 6 bájt lenne, de 1 változó maximum 4 bájt lehet, tehát a kódot úgyis módosítani kell, ha meg arra gondoltak, hogy legalább a paramétereket nem kell majd szegény scriptereknek minden sorban átírni, az megint hülyeség, hisz ki adna értéket olyan paraméternek, amely nincs használva?...Nos mindegy, nem a kritizálásért vagyunk most itt. Ha megnézzük, a kód 8-as shiftelést használ, mivel egy objektum adatait 8 bit tárolja. A mûködési elv ugyanaz, mint az elõzõ encode függvényeknél, így erre nem kívánok megint kitérni.Nézzük azonban hogyan írnánk ehhez egy decode függvényt. Mikor megkérdeztem errõl ismerõsömet, õ azt mondta, ugyanúgy mint az elõzõ, decode_panels függvénynél, csak 8-as shiftelést használunk, és 15 helyett 255-el végezzük a bitenkénti ÉS mûveletet. 255-el? minek?No erre is ki fogok térni késõbb, de haladjunk a szokásos módon. 1 bemeneti paraméterünk van, és 4 kimeneti, vagyis a header rész a következõ: decode_doors(doors, &bonnet, &boot, &driver_door, &passenger_door) Ezzel meg is vagyunk, itt nem látok különösebb magyarázni valót. Én most nem fogok törõdni a 2 nem létezõ ajtóval, ha egy jövõbeni verzióban netán azokat is behoznák mint lekérdezhetõ objektum, akkor ígyis-úgyis módosítani kell majd a kódot, addig meg úgysincs rá szükségünk.Nos, mint az eddigi decode függvényeknél, elsõ feladatunk egy shiftelés mentes bitenkénti ÉS mûvelet elvégzése. Az egyik megoldás a következõ: bonnet = doors & 255; Lehet így is. Azonban véleményem szerint felesleges itt egy ekkora számmal \"szenvednünk\". Miért használjunk ekkora számot, és ezáltal bonyolítsuk feleslegesen a kódunkat, ha nekünk a 8 bitbõl csak az elsõ 3 lesz ami adatot tárol? Bõven elegendõ egy olyan szám, amely bináris alakja 111. Ez nem más, mint a 7. Tehát a következõ megoldás ugyanúgy helyes: bonnet = doors & 7; A két kód pontosan ugyanazt az eredményt fogja visszaadni, semmi különbség a kettõ között. Csupán elsõ esetében nagy számmal dolgozunk, még második verzió esetén csak akkorával, amekkora szükséges.A kód többi részében csak 8-as shifteléssel végigcammogunk a doors változó többi bitjén is: boot = doors >> 8 & 7;driver_door = doors >> 16 & 7;passenger_door = doors >> 24 & 7; A teljes kód egyben: decode_doors(doors, &bonnet, &boot, &driver_door, &passenger_door){ bonnet = doors & 7; boot = doors >> 8 & 7; driver_door = doors >> 16 & 7; passenger_door = doors >> 24 & 7;} A kód pont ugyanazon az elven épül fel, mint az összes többi decode függvényünk, csak épp a benne használatos értékek az \"ajtó\" objectumok adatstruktúrájához vannak igazítva.2.6 Egyéb ötletek gyakorlati alkalmazásra, hasznosításraJoggal felmerülhet a kérdés, hogy ennyi? Emiatt rágtuk végig magunkat ezen néhány tízezer karakteren? Nem. Nem ennyi. A SA:MP-ban rengeteg felhasználási módja lehet még a bitmûveleteknek. Mutatok is néhány dolgot, mire jók még.2.6.1 RGBA->HEX; HEX->RGBA konvertálásokNos, bármily \"hihetetlen\", erre is használható. Miért is? Nos, 16-os számrendszerben 0-9, valamint A-F \"szánjegyek\" léteznek. A hexadecimális számjegy decimális értékben 0-15 lehet. Ha megnézzük bináris értékeket, 0-15 az 4 bittel leírható, hiszen a 15 az bináris alakban 1111. De ha józan paraszti ésszel belegondolunk is. 16 az 24. Így tehát 1 hexadecimális érték, az 4 bináris értékkel írható le. Ebbõl már sejthetõ, hogy itt is shiftelésre lesz szükségünk. Naná Mivel 4 bit ír le egy hexadecimális számjegyet, így 4-esével kell majd shiftelnünk.Mi is az az RGBA?RGBA: A színek olyan meghatározási módja, melyben a 3 alapszín([r]ed, [g]reen, lue) mellett egy 4. érték, az úgynevezett [a]lpha is szerepel, amely az áttetszõséget adja meg. A legtöbb program(pl. paint, PS), és pl. a css(külön függvénnyel!) is elfogadja ezen értékeket decimális számokként(0-255) is, a SA:MP-ban azonban hexadecimális alakban kell megadnunk. Bármily furcsa a következõ 4 sor pont ugyan azt jelenti:\"Normál\" RGB alak(pl. paint, photoshop): Piros: 0, Zöld: 255, Kék: 255CSS alak(függvénnyel): rgba(0, 255, 255, 1);Pawn alak(1): 0x00FFFFFFPawn alak(2): 16777215 Ó igen, a Pawn-ban decimális számokként is megadhatjuk a színeket, hisz a program mindent bináris számrendszerbe fog átszámolni. De akár bináris alakban is megadhattuk volna:Pawn alak(3): 0b111111111111111111111111 Az, hogy mi a Pawn-nak hexadecimális alakban szoktuk megadni, annak az egyetlen oka, hogy így a leginkább \"átlátható\", és ez egy amúgy is elfogadott módja (mármint a hexadecimális számmal való megadás) a színek leírásának a számítástechnikában.Nos, mivel 1 szín 0 és 255 közötti értéket tartalmaz a piros, zöld és kék színekbõl, ez hexadecimálisan 2 számjeggyel leírható: 00-FF. Az FF értéke 255, aki nem hiszi, számoljon utána (162-1)2 hexadecimális számjegy az pedig 8 bináris számjeggyel leírható(00000000-11111111). Így már láthatjuk, hogy 8-as shiftelésre lesz szükségünk. Másra nincs is szükségünk, az RGBA->HEX konverter elkészítéséhez.stock RGBAToHex( r, g, b, a ){return a | b << 8 | g << 16 | r << 24;} Az, hogy miért fordított sorrendben kellett megadnunk az értékeket, az megint csak azzal magyarázható, hogy a biteket hétköznapi értelemben véve visszafelé számozzuk.Egy ellentét, azaz HexToRGBA függvény megírása sem túl bonyolult, ugyan ezt kell csinálnunk, csak visszafelé.stock HexToRGBA( colour, &r, &g, &b, &a ){a = colour & 0xFF;b = (colour >> 8 ) & 0xFF;g = (colour >> 16 ) & 0xFF;r = (colour >> 24 ) & 0xFF;} A 0xFF-et természetesen decimális értékkel is megadhatjuk(255).No akkor lehet ugye RGBA-t ARGB-be is konvertálni, az így néz ki:RGBAToARGBHEX(r,g,b,a){return (r & 0xff) << 24 | (g & 0xff) << 16 | (b & 0xff) << 8 | a & 0xff;} Ez máris egy újabb felhasználási módja a bitmûveleteknek. De, mutatok még 2.6.2 Boolen változók helyett IntegerAz al-al-alcímet elolvasva joggal felmerülhet a kérdés, hogy ez meg hogy jön ide. Nos ez úgy jön ide, hogy most egy kis memóriatakarékosságról fogok itt regélni. Adhattam volna azt is címnek, hogy miért ne használjunk boolean változókat, ha legalább 5 igaz/hamis értéket el akarunk tárolni.Nos akkor egy kis memórialecke. Azt már tisztáztuk, hogy az integer változók 32 bitesek, vagyis 4 bájtot foglalnak le a memóriában maguknak. A boolean típusú változókról azonban még nem volt szó. Mivel 1 értéket tárolnak csak, az is csak igaz vagy hamis lehet, így joggal gondolhatnánk, hogy 1 bit a mérete. Azonban nem...1 boolean változó 8 bitet, azaz 1 bájtot foglal le magának a memóriában. Hogy ez miért így van, az a számítógépek memóriahasználatával magyarázható. Manapság nincs mód arra, hogy a memóriában egyetlen bitet módosítsunk, csak 8-as \"csomagokban\", vagyis bájtonként tudjuk ezt megtenni. Hogy ez miért van így, azt most nem fogom itt fejtegetni Nos, ha megnézzük tehát, a boolean változó egy nagyon-nagyon pazarló megoldás. Hiszen 1 darab boolean változó 1 bájtot foglal, 4 darab boolean változó esetén ez már 4 bájt, ami megegyezik 1 integer változó méretével. Azonban, még a 4 boolean változóban 4 igaz/hamis érték kerülhet tárolásra, addig egy integerben 32 igaz/hamis értéket is eltárolhatunk.Bizony, 32 igaz/hamis érték. Hiszen 1 integer változó 32 bitbõl áll, ha minden bitet hasznosítunk, akkor a bitmûveletek felhasználásával 32 igaz/hamis értéket is eltárolhatunk. Összehasonlítva:32 darab boolean változó mérete 32 bájt.1 integer változó mérete: 4 bájt. Vagyis 32 boolean változó esetén nyolcad annyi memóriánkba kerül, ha egy darab integerben tároljuk el az értékeket. Persze 32 booleanra ritkán van szükség, de egy RP modban simán elõfordulhat. Gondoljunk csak bele... Van 32 boolean-tömbös változónk, ez a maximum slotszámot(1000) figyelembe véve: 32*1000 byte azaz 32000 byte ami ~31 kilobyte. Ha ezt egyetlen integer-tömbbel oldanánk meg: 4*1000 byte, azaz 4000 byte, ami ~3,9 kilobyte. Azért így nézve már nem annyira \"elhanyagolandó\" az a különbség szerintem =)Akár írhatnánk erre külön egy függvényt, mely az adott bit értékével tér vissza. Nem is bonyolult. Így már azok számára is használhatóak a bitmûveletek, akik nem értenek hozzá, és rengeteg memóriát takaríthatnak meg.Íme egy függvény az adatok kiolvasására:stock GetData(integer, bit){ return integer >> bit & 1; //vagy az 1 helyett akár 0x01 alakban is megadhatjuk, nincs jelentõsége} És a párja, adat írásra:stock SetData(&integer, bit, value){ integer |= value << bit; return 1;} Persze ezt még lehetne csinosítani, hogy pl. ne 0-tól hanem 1-tõl számoljuk a biteket, vagy hogy 31(vagy 32 ha 1-tõl számolunk) felett 0 legyen a return értéke, esetleg a value-re is rakhatnánk ellenõrzést, hogy 0-1-e az értéke, vagy más, de ilyenekkel most nem törõdtem.2.6.3 Több érték, egy integerbeNa ez az a rész, ami véleményem szerint már csak a nagyon megrögzött memóriafelszabadítók szerint nem lesz felesleges Amennyiben nincs szükségünk túl nagy számokra(pl. csak 0-15, vagy akár csak 0-7, de akár még a 0-255 is megfelel), akkor akár egy integerbe is tehetjük õket. Hogyan? Egyszerû. Csoportosítjuk a biteket. Tegyük fel, hogy maradván egy RP szervernél, el akarjuk tárolni ûbergigantikusan memóriakímélõ megoldással a játékos házának számát(ez legyen mondjuk 0-100 közötti szám), a játékos frakcióját(ez legyen 0-15 között), a játékos rangját(0-7 között), hogy a telefonja be van-e kapcsolva(0-1), a játékos szolgálati jármûvének id-jét(legyen 0-255 között), és az alapmunkáját(0-15 között). Ez is megoldható, egyetlen integerben, és ismételten 1000 slottal számolva máris megspóroltunk 4*4*1000+1*1000 byte-t(5 integer, és 1 boolean helyett csupán 1 integert használunk), ami ~16,6 kilobyte. Azért nem mindegy az, hogy 20,5 kb-ot vagy 4 kb-ot használunk szerintem... Ezt a következõ módszerrel valósíthatnánk meg:#define DATA_HOUSE 0#define DATA_FACTION 1#define DATA_RANK 2#define DATA_PHONESTATE 3#define DATA_DUTYCAR 4#define DATA_JOB 5stock GetData(playerinfo, data){ switch(data) { case DATA_HOUSE: return playerinfo & 127;//7 bit case DATA_FACTION: return playerinfo >> 7 & 15;//4 bit case DATA_RANK: return playerinfo >> 11 & 7;//3 bit case DATA_PHONESTATE: return playerinfo >> 14 & 1;//1 bit case DATA_DUTYCAR: return playerinfo >> 15 & 255;//8 bit case DATA_JOB: return playerinfo >> 23 & 15;//4 bit}return -1;}stock SetData(&playerinfo, data, value){ switch(data) { case DATA_HOUSE: { playerinfo = playerinfo & ~127 | value; }//7 bit (0-127 közötti maximálisan) case DATA_FACTION: { playerinfo = playerinfo & ~(15 << 7) | value << 7; }//4 bit (0-15 között) case DATA_RANK: { playerinfo = playerinfo & ~(7 << 11) | value << 11; }//3 bit (0-7 között) case DATA_PHONESTATE: { playerinfo = playerinfo & ~(1 << 14) | value << 14; }//1 bit (0-1) case DATA_DUTYCAR: { playerinfo = playerinfo & ~(255 << 15) | value << 15; }//8 bit (0-255 között) case DATA_JOB: { playerinfo = playerinfo & ~(15 << 23) | value << 23; }//4 bit (0-15 között) } return -1;}stock SetDatas(&playerinfo, house, faction, rank, phonestate, dutycar, job){playerinfo |= house | (faction << 7) | (rank << 11) | (phonestate << 14) | (dutycar << 15) | (job << 23);} Ez már azért elég morbid, ezt inkább csak érdekességképp mutattam, de éppenséggel ez is használható lenne, csak ehhez már kell egy kis õrültség 2.7 Második fejezet végeNos, láthattuk hogyan is mûködik mindazon sok \"marhaság\", amit az elsõ fejezetben elméletként átvettünk. Láthattuk hogyan mûködnek a jármûvek paneljének adataival babráló encode függvények, valamint megmutattam, hogyan írhatunk hozzájuk decode verziókat. Láthattunk még példát egyéb gyakorlati hasznosításra, kicsit megkoccoltuk a hexadecimális számok világát is, majd szóba került egy kis memóriatakarékosság. Végül mutattam egy összetettebb, elvetemült példát is, a bitmûveletek hasznosítására.3. Összegzés, végszó Úgy gondolom, hogy elég részletesen körbe jártuk a témát. Megnéztük a bitmûveletek elméleti, és gyakorlati oldalát is, láthattunk gyakorlati példákat. Nem kell elsõre megérteni ezt az egészet, sõt... Elegendõ ha egyszer elolvassuk, emésztjük kicsit, majd késõbb megint elõvesszük, akkor netán már kicsit többet is megértünk belõle. Nem egy kihagyhatatlan része a Pawno-nak, de azért van annak haszna, ha az ember jártas ebben a témában is, bár sokak szerint nem éri meg a sok fáradtságot, és idõt, mely a megértéséhez, begyakorlásához, használatának elsajátításához szükséges. Próbáltam mindent részletesen kifejteni. A bonyolultabb dolgokhoz sokat magyarázni, az egyszerûbbek esetén csak visszautalni az alapokhoz. Sok dolgot talán már túl is magyaráztam. Ezt a leírást nem 1 nap alatt készítettem el, kb. 1 hónapja írtam meg az elméleti oldalát, most az elmúlt pár napban pedig a gyakorlatot. Esténként 1-2 órát szántam rá csupán, és mivel ilyenkor már nem vagyok épp a toppon, elképzelhetõ, hogy néhány helyen helyesírási, vagy elvi hibát vétettem. Amennyiben valaki ilyet találna, kérem szóljon, és korrigálni fogom. Nos, azt hiszem nincs is más hátra, mint hogy sok szerencsét kívánjak a bitmûveletek elsajátításához, és megosszam a forrásokat. Remélem néhányan hasznosnak találják, eme igen hosszúra sikerült leírást.4. Források: Internet:WikipédiaSA:MP WikiSA:MP hivatalos fórumKönyv, e-book:Pohl László - A programozás alapjai(BME - Elektronikus Eszközök Tanszéke, Budapest, 2010)Siroki László - C programozás - Bitmûveletek jegyzeteˆ Anthony, 2012 - Minden jog fenntartva.A leírás egészben, vagy részleteiben szabadon terjeszthetõ, ameddig egyértelmûen fel van tüntetve az eredeti készítõ, valamint hogy honnan is származik. « Utoljára szerkesztve: 2012. augusztus 01. - 10:53:54 írta Anthony » Naplózva
A B A|B000101011111[/quote]Példának nézzük meg, mi történik, ha a bitenkénti VAGY operátort alkalmazzuk, szintén a 11 és a 7 esetén. A 11 bináris számrendszerben mint már írtam 00000000000000000000000000001011-nek felel meg, a 7 pedig 00000000000000000000000000000111-nek. Tehát a 11 | 7, az ugyaz mint ha ezt írnám: 00000000000000000000000000001011 | 00000000000000000000000000000111. Az átláthatóság kedvéért ismét egymás alá fogom írni a 2 számot, hogy áttekinthetõbb legyen, és az eredményt pedig a 3. sorba: 00000000000000000000000000001011|0000000000000000000000000000011100000000000000000000000000001111[/quote]Eredményül tehát 00000000000000000000000000001111-et kaptunk, ami a kettes számrendszerben a 15-nekk felel meg. A következõ 2 sor tehát ugyanazt jelenti: Bin:00000000000000000000000000001011|00000000000000000000000000000111=00000000000000000000000000001111Dec:11|7=15[/quote]Megint néhány példa: 00000000000000000000000000000011 | 00000000000000000000000000000101 = 0000000000000000000000000000011100000000000000000000000001110110 | 00000000000000000000000001011001 = 00000000000000000000000001111111[/quote]1.4.3 Bitenkénti (bitek közötti) KIZÁRÓ VAGY mûveletA bitenkénti KIZÁRÓ VAGY mûvelet (XOR, ^) egy logikai operátor. Ez az operátor nagyon hasonlít a VAGY operátorra, az a fõ különbség a kettõ között, hogy amennyiben mindkét bit 1-es, akkor a KIZÁRÓ VAGY operátor 0-t ad eredményül, a többi esetben ugyanúgy viselkedik mint a VAGY operátor. Igazság táblázata: A B A^B000101011110[/quote]Példának nézzük meg, mi történik, ha a bitenkénti KIZÁRÓ VAGY operátort alkalmazzuk, szintén a 11 és a 7 esetén. A 11 bináris számrendszerben mint már írtam 00000000000000000000000000001011-nek felel meg, a 7 pedig 00000000000000000000000000000111-nek. Tehát a 11 ^ 7, az ugyaz mint ha ezt írnám: 00000000000000000000000000001011 ^ 00000000000000000000000000000111. Az átláthatóság kedvéért ismét egymás alá fogom írni a 2 számot, hogy áttekinthetõbb legyen, és az eredményt pedig a 3. sorba: 00000000000000000000000000001011^0000000000000000000000000000011100000000000000000000000000001100[/quote]Eredményül most 00000000000000000000000000001100-t kaptunk, ami a kettes számrendszerben a 12-nek felel meg. Érdekes, hogy teljesen más eredményt kapunk, pedig lényegében ugyanazt az eredményt adja az operátor, - kis különbséggel ugyebár - mint a VAGY operátor.A következõ 2 sor tehát ugyanazt jelenti: Bin:00000000000000000000000000001011^00000000000000000000000000000111=00000000000000000000000000001100Dec:11^7=12[/quote]Megint néhány példa: 00000000000000000000000000000011 ^ 00000000000000000000000000000101 = 0000000000000000000000000000011000000000000000000000000001110110 ^ 00000000000000000000000001011001 = 00000000000000000000000000101111[/quote]Ezzel kapcsolatban egy érdekességet mutatnék meg. Vegyünk 2 számot. A látványosság kedvéért én példának a 118-at és a 89-et veszem. 1. szám: 00000000000000000000000001110110 = 1182. szám: 00000000000000000000000001011001 = 89Mûvelet elvégzése: 00000000000000000000000001110110 ^ 00000000000000000000000001011001 = 00000000000000000000000000101111 = 471. szám az eredmény, 2. szám az eredeti 2. szám: 00000000000000000000000001011001 = 89Eredmény: 00000000000000000000000000101111 ^ 00000000000000000000000001011001 = 00000000000000000000000001110110 = 118[/quote]Vagyis visszakaptuk a kiindulási állapotot 1.4.4 Bitenkénti (bitek közötti) NEGÁLÁSA bitenkénti NEGÁLÁS (NOT, ~) az utolsó logikai operátor, melyet meg fogok mutatni. Ez az operátor eléggé eltér az eddigiektõl, leginkább abban, hogy ezt a mûveletet nem 2 bitre alkalmazzuk, hanem csak 1-re. Szokás komplementer-operátornak is hívni(konyhanyelven). Lényegében az 1-esekbõl 0-t, a 0-kból 1-est csinál. Igazság táblázata: A ~A0110[/quote]Példának nézzük meg, mi történik, ha a bitenkénti NEGÁLÁST alkalmazzuk a 7-es számra. A 7 bináris számrendszerben mint már írtam 00000000000000000000000000000111-nek felel meg. Tehát a ~7, az ugyaz mint ha ezt írnám: ~00000000000000000000000000000111. Nézzük tehát, mi történik: ~0000000000000000000000000000011111111111111111111111111111111000[/quote]Eredményül most 11111111111111111111111111111000-t kaptunk, ami a kettes számrendszerben a -8-nak felel meg. A következõ 2 sor tehát ugyanazt jelenti: Bin:~00000000000000000000000000000111=11111111111111111111111111111000Dec:~7=-8[/quote]Megint néhány példa: ~00000000000000000000000000000101 = 11111111111111111111111111111011~00000000000000000000000001110110= 11111111111111111111111110001010[/quote]Ehhez kapcsolódóan lenne itt egy fontos megjegyzendõ dolog. Aki nem érti, hogy miért -8 a megoldás, az nem olvasta el elég rendesen a \"Részletesebben a bitekrõl a Pawn-ban\" részt.1.4.5 Biteltolás (arithmetic shift, shiftelés) operátor(ok)A shift operátorok(<<, >>) a bitek tologatására használhatóak. A << balra tol, a >> jobbra. Bal oldalt található a szám, amit tologatni kívánunk, jobb oldalt pedig az, hogy mennyivel szeretnénk eltolni.1.4.5.1 Jobbra toló operátor(>>):Ez az operátor a szám bitjeit jobbra tolja a megadott mértékkel. Egy bittel való eltolás, 2-vel való osztásnak felel meg. Nézzünk egy példát: 00000000000000000000000000001000//8>>200000000000000000000000000000010//2[/quote]Láthatjuk, hogy semmi más nem történt, mint minden bitet 2-vel hátrébb mozgattunk. Az így \"megüresedett\" bitek helyére pedig 0-k kerültek. Lényegében az történt, hogy az utolsó 2 bit kiesett, az így megmaradt biteket 2-vel hátrébb csúsztattuk, majd az elsõ 2, így \"üres\" bit 0 értéket kapott.Nézzünk egy példát negatív számmal: 11111111111111111111111111110000//-8>>211111111111111111111111111111100//-2[/quote]Láthatjuk, hogy itt fontos szerepe van az MSB-nek. Ettõl függ ugyanis, hogy 0-k vagy 1-esek kerülnek a \"megüresedett\" bitek helyére. Fontos tehát megjegyezni, hogy a jobbra shiftelés megtartja a számok elõjelét, nem változtat azon!1.4.5.2 Balra toló operátor(<<):Ez az operátor a szám bitjeit balra tolja a megadott mértékkel. Egy bittel való eltolás, 2-vel való szorzásnak felel meg. Nézzünk egy példát: 00000000000000000000000000001000//8<<200000000000000000000000000100000//32[/quote]Itt megint könnyedén észrevehetõ mi is történt. Minden bitet fentebb \"toltunk\" 2-vel. Azok a bitek, amelyek ezáltal a 32. bit elé kerültek, kiesnek. Az így megüresedett bitek 0-kkal egészültek ki. Eddig nem túl bonyolult. Azonban figyeljünk oda, ha túl sokáig shiftelünk egy pozitív számot, akkor az utolsó érték, melyet fel tud venni a változónk (mielõtt még túlcsordulás miatt elérné a 0-t), az egy negatív szám!Nézzük mi történik negatív számok esetén: 11111111111111111111111111111000//-8<<211111111111111111111111111100000//-32[/quote]Láthatjuk hogy a balra shifteléskor a szám mindig 0-kkal egészítõdik ki, legyen az akár negatív, akár pozitív. Nem veszi figyelembe az MSB értékét, így ha túl sokat shiftelünk, akkor elõfordulhat, hogy a szám elveszti negatív értékét, és eléri a 0-t.1.4.6 Logikai biteltolás (logical shift, logikai shiftelés) operátorElõször is le kell tisztázni, hogy logikai shiftelésbõl csak egy létezik, jobbra logikai shiftelés(>>>). Sokban hasonlít a jobbra shiftelõ operátorra (>>), azonban van egy fontos különbség.Nézzük az elsõ példát: 00000000000000000000000000001000//8>>>200000000000000000000000000000010//2[/quote]Eddig nem igazán tapasztalhattunk semmi változást, minden pont ugyanúgy zajlott, mint a jobbra shiftelésnél(>>).Nézzük mi történik negatív szám esetén: 11111111111111111111111111111000//-8>>>200111111111111111111111111111110 //1073741822[/quote]Itt már láthatjuk a különbséget. Logikai shiftelés esetén az üres bitek helyére bekerülõ érték nem függ az MSB-tõl, hanem mindig 0. Emiatt soha nem kaphatunk eredményül negatív számot, logikai shiftelés esetén.1.5 Elsõ fejezet végeNos, ezennel átvettünk mindent, amit érdemes tudni a bitmûveletekrõl. Megmutattam az alapok, hogy hogyan épülnek fel a változók, valamint átvettük a fõ operátorokat. Eddig csak színtiszta elmélet volt. A következõ részben megmutatom, hogyan hasznosíthatjuk mindezt a gyakorlatban, vagyis mire is jó az a sok marhaság, amit eddig leírtam.2. Gyakorlati példák, érdekességek 2.1 Áttekintés, mikrõl is lesz még szóAz alábbiakban még pár dolgot szeretnék mutatni. Elõször is néhány \"függvénnyel\" fogok foglalkozni, majd mutatok még egy-két érdekességet. Konkrétan megmutatom, hogyan tudjuk egy jármû minden \"paneljének\" adatát kiíratni, részletesen(erre alapból nincs lehetõség, és a függvények is mind \"encode\" függvények, én viszont mutatok egy-egy decode \"verziót\" is.)Az alábbi 4 függvénnyel fogok foglalkozni: encode_tiresencode_panelsencode_doorsencode_lightsDe elõbb egy kis háttérinfó:A Pawnban a forrásban megadhatunk bináris számokat ugyanúgy, ahogyan Hex számokat is megadhatunk. Ehhez a \"0b\" prefixet kell használnunk. Tehát ha bináris számunk az 1011 és ezt akarjuk adni értékül egy változónak, azt így tehetjük meg: new var = 0b1011; 2.2 encode_tiresHáttér információ:A SA:MP-ban egy jármû abroncsainak adataid 4 bit tárolja. Az elsõ bit a jobb hátsó, a második bit a jobb elsõ, a harmadik bit a bal hátsó, a negyedik bit pedig a bal elsõ abroncsról tárol információt. Mivel 4 bitben eltárolhatóak az információk, így decimális formában 0-15 értékekkel is be lehet állítani a jármûvek abroncsait. 16-tól kezdve az értékek már ismétlõdni kezdenek, szóval a 16-nál az eredmény ugyanaz mint 0-nál, 17-nél ugyanaz mind 1-nél, és így tovább. Ez érthetõ, ha tudjuk hogyan épül fel a kettes számrendszer.(Ne felejtsük el, a biteket hátulról számozzuk!)Nos nézzük hogyan néz ki a kód: encode_tires(tire1, tire2, tire3, tire4){ return tire1 | tire2 << 1 | tire3 << 2 | tire4 << 3;} Láthatjuk, hogy bekérjük a 4 abroncs adatát(vagyis hogy ép[0], vagy kidurrant[1]), majd egy sima bitenkénti VAGY mûvelet és biteltolás kombinálásával visszatérünk az eredménnyel. A kódban a tire1 felel meg a jobb hátsó abroncsnak, a tire2 a jobb elsõ, a tire3 a bal hátsó, és a tire4 a bal elsõnek. Nézzünk egy konkrét példát. Tegyük fel, hogy én a hátsó 2 abroncsot ki akarom durrantani, még az elsõ kettõt épre akarom beállítani. Ez esetben így néz ki a dolog: encode_tire(1,0,1,0); Ha kiíratnánk konzolba a visszatérési értéket (%d-vel), 5-öt kapnánk. Miért is? Az 5 bináris alakja 0101. Hoppá, ha megnézzük ez pont fordítottja annak, amit mi megadtunk számjegyekként, csak már konkrét bináris számként. Nézzük hogyan is történhetett mindez, mit is csináltunk. A függvény a következõ mûveletet végzi: tire1 | tire2 << 1 | tire3 << 2 | tire4 << 3 Konyhanyelven úgy magyarázhatnánk el a dolgot, hogy ez a kód egymás mellé pakolgatja a megadott számokat, fordított sorrendben. Na de mi most nem a konyhanyelv miatt vagyunk ebben a leírásban, szóval nézzük a kódunkat. Lényegében ez futott le, a konkrét értékeinkkel nézve: 1 | 0 << 1 | 1 << 2 | 0 << 3 Bármily furcsa is, ez egy mûvelet, melyen szépen végig halad sorban a program. Elõször is írjuk át az egészet bináris alakba(az egyszerûség kedvéért most csak 4 bitet fogok kiírni): 0001 | 0000 << 1 | 0001 << 2 | 0000 << 3 Na erre se mondtuk volna rá a tutorial elsõ részének elolvasása elõtt, hogy ez egy mûveletsor. A rendszer elsõnek az eltolásokat végzi el (olyan ez mint a 2*3+2*4+2*5 mûveletben elõször a szorzásokat végezzük el, és csak utána adunk össze, ha nincs zárójel), és csak utána a bitenkénti VAGY mûveleteket, ezért mi is így fogunk tenni. Emlékezzünk, eltoláskor az \"üres\" bitek 0 értéket fognak felvenni. Végezzük el külön csak az eltolásokat: 0000 << 1 = 00000001 << 2 = 01000000 << 3 0000 Nos, ha ezeket elvégeztük, akkor nézzük, hogy is áll most a kódunk: 0001 | 0000 | 0100 | 0000 Négy azonos mûvelet van hátra, így hát a program innen más szépen sorban fog haladni. Tegyünk mi is így. Elsõ tehát a 0001 | 0000 mûvelet elvégzése. Emlékeztetõül, a bitenkénti VAGY mûvelet akkor ad 0-t eredményül, ha mind a kettõ érték 0, minden más esetben 1-et. Végezzük el külön az elsõ bitenkénti VAGY mûveletet: 0001 | 0000 = 0001 Nem volt bonyolult, nézzük tehát hogy áll akkor most a teljes mûveletünk: 0001 | 0100 | 0000 Haladunk szépen tovább, elvégezzük a 0001 | 0100 mûveletet. 0001 | 0100 = 0101 Nincs mit magyarázni rajta, egyszerû bitenkénti VAGY mûvelet volt ez is. Már csak ez van hátra: 0101 | 0000 Nos, ez sem lesz egy bonyolult mûvelet: 0101 | 0000 = 0101 Ezzel meg is kaptuk a végeredményt, ami nem más, mint a 0101 (tízes számrendszerben 5). Mivel azonban eléggé kusza lett így a számításunk, nézzük egyben hogyan is néz ki az egész, ha nem bontom ennyire részekre: 0100 | 0000 << 1 | 0001 << 2 | 0000 << 3v0001 | 0000 | 0100 | 0000v0001 | 0100 | 0000v0101 | 0000v0101 Nos, ezennel láthattuk a gyakorlatban is, hogyan mûködik a függvény. Kijelenthetjük tehát, hogy a függvény a számokat a sorrendjüknek megfelelõ számú bitbe helyezi. Mivel azonban a biteket hátulról számozzuk, így az 1,0,1,0 bemenetek végeredménye 0101 lesz. Így már nem is olyan bonyolult Na, ha már ilyen sokáig eljutottunk, akkor felhasználhatjuk ezt a kódot arra, hogy az alapjait figyelembe véve elkészítsük a decode változatot. Mivel 4 értékkel térünk vissza, ezért paraméterként kell majd 4 változó, melyeknek módosíthatjuk az értékét, illetve 1 paraméter pedig a dekódolandó szám. A függvényünk \"címsora\" tehát így néz ki: decode_tires(tires, &tire1, &tire2, &tire3, &tire4) Hogy példánk ne csak elmélet legyen, a tires változó legyen az elõbbi 0101 értékkel egyenlõ. Na akkor menjünk tovább. Bemeneti értékünk ugye a \"tires\" változóban lesz eltárolva, így ezzel fogjuk a mûveleteket végezni. Kezdjünk el gondolkozni. Hogyan is tudnánk ellenõrizni egy adatot. Legegyszerûbb ha a bitenkénti ÉS mûveletet alkalmazzuk. Miért is? Nos, a bitenkénti ÉS mûvelet ugye akkor ad eredményül 1-et, ha mind a 2 érték egy. Tehát ha min egy számot összehasonlítunk a 1-el(binárisan ugye 00000000000000000000000000000001, akkor az eredményünk lényegében arról fog tájékoztatni, hogy az elsõ bit 0, vagy 1. Nem is olyan bonyolult ez. Nézzük akkor az elsõ bitet. Arra emlékezhetünk még, hogy az elsõ bit a tire1, vagyis jobb hátsó abroncs adatait tárolja. Tehát: tire1 = tires & 1; Vagy konkrétan a példánk esetében: tire1 = 0101 & 0001 Mivel a bitenkénti ÉS mûvelet akkor ad 1-et eredményül, ha mind a kettõ bit 1, akkor már láthatjuk is, hogy miért elegendõ az elsõ bitet szemügyre venni. Mivel mind a 2 helyen az elsõ bit 1, ezért a tire1 változónk értéke is 1(0001) lesz. Egyszerû és nagyszerû, máris megkaptuk a jobb hátsó abroncs állapotát.Gondolkozzunk hát tovább. Nekünk most arra van szükségünk, hogy a 2. bit értékét megkapjuk. Hogyan kaphatjuk ezt meg? Legegyszerûbb megoldás, ha a tires változót jobbra shifteljük 1-el, majd ismételten bitenkénti ÉS mûveletet végzünk. Nézzük tehát, hogyan is nézne ez ki: tire2= tires >> 1 & 1; Vagy konkrétan a mi példánkban: tire2 = 0101 >> 1 & 0001vtire2 = 0010 & 0001 Ismételten bitenkénti ÉS mûvelet, és az eredményünk 0, mivel csak az egyik bit 1, a másik 0, így a bitenkénti ÉS mûvelet eredménye is 0. Ezen az elven megcsináljuk a maradék 2 abronccsal is: tire3= tires >> 2 & 1;tire4= tires >> 3 & 1; Vagy a mi esetünkben konkrét példánkkal: tire3 = 0101 >> 2 & 0001 // azaz: --> 0001 & 0001 = 1tire4 = 0101 >> 3 & 0001 // azaz: --> 0000 & 0001 = 0 A teljes kódunk tehát: decode_tires(tires, &tire1, &tire2, &tire3, &tire4){ tire1 = tires & 1; tire2 = tires >> 1 & 1; tire3 = tires >> 2 & 1; tire4 = tires >> 3 & 1;} A konkrét példánkban a 4 változónk értéke a függvény lefutása után: tire1: 1tire2: 0tire3: 1tire4: 0 Ha összehasonlítjuk, ezek pont azok az értékek, mint amiket az encode függvényben megadtunk, tehát úgy tûnik jól dolgoztunk 2.3 encode_lightsEz a rész lesz a legrövidebb. Az egész lámpás rész pontosan ugyanarra a sémára épül mint az abroncsok. Az adott bitek ugyanazon a \"helyen\" lévõ lámpa adatait szolgáltatják, ugyanúgy 0 az ép, és 1 a sérült lámpák állapot azonosítója, és ugyanúgy 1-1 bit tárolja az értékeket.Maga a kód: encode_lights(light1, light2, light3, light4){ return light1 | light2 << 1 | light3 << 2 | light4 << 3;} Láthatjuk hogy halál pontosan ugyanaz mint az encode_tires, csak más változónevekkel. Így hát a decode függvény is ugyan az lesz, csak a változóneveket kell módosítanunk: decode_lights(lights, &light1, &light2, &light3, &light4){ light1 = lights & 1; light2 = lights >> 1 & 1; light3 = lights >> 2 & 1; light4 = lights >> 3 & 1;} Ha megnézzük látjuk, hogy teljesen ugyan az a séma, ugyan az a felépítés, minden megegyezik, ezért nem is írtam le újból ugyanazt, amit egyszer már kifejtettem.2.4 encode_panelsHáttér információ:A SA:MP 7 panelrõl tárol adatokat. Egy panel adatait 4 bit írja le. Nos, ennél több infóra nekünk most nincs szükségünk, az elõzõ kód alapján ezen kód is megérthetõ, így az encode verzióhoz nem is nagyon szeretnék magyarázatot fûzni. A különbség az encode_tires-hez képest, hogy itt 4-essével shiftelünk, mivel 1 panel adatait 4 bit tárolja.Maga a kód: encode_panels(flp, frp, rlp, rrp, windshield, front_bumper, rear_bumper){ return flp | frp << 4 | rlp << 8 | rrp << 12 | windshield << 16 | front_bumper << 20 | rear_bumper << 24;} Nézzünk egy decode verziót, ehhez a kódhoz. Alapjaiban ugyanazon a vonalon kell elindulnunk, mint a decode_tires esetében. Itt most 7 visszatérési értékünk lesz, és 1 bemeneti érték. Tehát így néz ki a függvény header: decode_panels(panels, &flp, &frp, &rlp, &rrp, &windshield, &front_bumper, &rear_bumper) A folytatásban megint csak az elõbbi verziót kell alapul vennünk, azonban itt lesz egy kis különbség. Egyértelmû, hogy mivel 1 panel adatait 4 bit tárolja, így a 4 bitet nekünk egyben kell lekérni, és egyben kell vele visszatérnünk. Megint csak a bitenkénti ÉS mûveletet fogjuk alkalmazni, azonban mivel 4 bitrõl van szó, így egy olyan számmal kell összehasonlítanunk, mely bináris alakban 1111. Ez a szám nem más, mint a 15. Hogy miért ez kell?Ha egy bitsorozat tartalmát ki szeretnénk másolni egy másik változóba, akkor a bitenkénti ÉS mûveletre vna szükségünk. A bitenkénti ÉS mûvelet akkor ad 1-et eredményül, ha mindkét bit 1, ha valamelyik 0, akkor a visszatérés már 0 lesz. Ergó tehát, ha egy csupa 1-esekbõl álló bitsorozattal vetjük össze az eredeti bitsorozatunkat, akkor lényegében lemásoljuk a sorozatot, egy másik változóba, hisz eredményül mindig az eredeti sozatot fogjuk visszakapni. Íme néhány példa: 0101 & 1111 = 01011111 & 1111 = 11110000 & 1111 = 00001011 & 1111 = 1011 Láthatjuk tehát, hogy ha csupa 1-est tartalmazó bitsorozattal hasonlítjuk össze az eredeti számunkat, akkor lényegében egy másolatot készítünk. Mivel nekünk erre a 4 bitre van szükségünk a 32-bõl, így pontosan ezt kell tennünk, ezen 4 bitet lemásolni egy másik változóba, hogy késõbb ezt majd egy másik függvénnyel még jobban darabokra szedhessük.Íme tehát akkor a kódunk elsõ sora: flp = panels & 15; Mivel 4-esével vizsgáljuk a biteket, így lényegében annyit kell még változtatnunk az elõzõ kódon(decode_tires), hogy az 1-esével növekvõ shiftelések helyett 4-esével shifteljünk. Azaz a 2. panel adatainak lekérdezése: frp = panels >> 4 & 15; Innen pedig már csak mindig további 4 shiftelést el kell végeznünk, és szépen végig haladhatunk a panels változó bitjein, és megkaphatjuk minden panel adatait külön-külön. rlp = panels >> 8 & 15;rrp = panels >> 12 & 15;windshield = panels >> 16 & 15;front_bumper= panels >> 20 & 15;rear_bumper = panels >> 24 & 15; Ezennel minden panel adatait megkaptuk. Ezután már akár a decode_tires függvényt felhasználva szét lehet szedni az adatokat darabokra, de akár egy saját névvel, és más változó nevekkel ellátott másik függvényt is létre lehet hozni a decode_tires mintájára.Tehát az egész kód egyben: decode_panels(panels, &flp, &frp, &rlp, &rrp, &windshield, &front_bumper, &rear_bumper){ flp = panels & 15; frp = panels >> 4 & 15; rlp = panels >> 8 & 15; rrp = panels >> 12 & 15; windshield = panels >> 16 & 15; front_bumper= panels >> 20 & 15; rear_bumper = panels >> 24 & 15;} Igazából a függvény ugyanarra a sémára épül mint a decode_tires, csak annyi a különbség, hogy 4 bitenként kapjuk az adatokat, nem pedig egyesével, mivel itt minden panelnek 4 adatát tudjuk majd még lekérdezni. Nem tudom lehet-e még ehhez a részhez hozzáfûzni valamit, szóval szerintem ugorjunk. 2.5 encode_doorsHáttér információ:A SA:MP képes minden jármû 4 \"ajtajának\" állapotát tárolni. Ezek az \"ajtók\"(Doors-t hogy fordíthatnám jobban?) konkrétan a motorháztetõ, csomagtartó, vezetõoldali ajtó, és anyósülés oldali ajtó. A 2 hátsó ajtó állapotát jelenleg még nincs lehetõségünk lekérdezni.Amikor lekérdezzük egy jármû \"ajtajának\" adatait, azt a SA:MP 4 bájtként(32 bit) adja vissza. Ebbõl a 4 bájtból 1 bájt(8 bit) jut minden egyes objektumra. Vagyis minden egyes objektum adatait 8 bit tárolja. Vagyis tárolhatná...A SA:MP csak az elsõ 3 bitet használja, a további bitek üresek lesznek, nincs funkciójuk, talán majd késõbb lesz. Az elsõ bit azt tárolja, hogy az objektum zárt[0], vagy nyitott[1](nem az jelenti, hogy bezárt, hanem becsukott/kinyitott) állapotban van-e. A második bit azt adja meg, hogy az objektum ép[0] vagy sérült[1]. A 3. bit arról tájékoztat, hogy az adott objektum rajta van-e még a jármûvön[0] vagy már leesett[1]. A többi bit mint mondtam üres. Ez majd a decode függvénynél lesz fontos.Nézzük tehát a konkrét kódot: encode_doors(bonnet, boot, driver_door, passenger_door, behind_driver_door, behind_passenger_door){ #pragma unused behind_driver_door #pragma unused behind_passenger_door return bonnet | boot << 8 | driver_door << 16 | passenger_door << 24;} A 2 hátsó ajtóval nem foglalkozunk, mivel azok jelenleg még nem támogatottak ugyebár, de a kód készítõje gondolt a jövõre is, vagy fogalmam sincs miért adta meg ezt a két paramétert is, hisz ígyis-úgyis módosítani kell a kódon... Ha behoznak még 2 ajtót, akkor az 1 bájtos tárolással már 6 bájt lenne, de 1 változó maximum 4 bájt lehet, tehát a kódot úgyis módosítani kell, ha meg arra gondoltak, hogy legalább a paramétereket nem kell majd szegény scriptereknek minden sorban átírni, az megint hülyeség, hisz ki adna értéket olyan paraméternek, amely nincs használva?...Nos mindegy, nem a kritizálásért vagyunk most itt. Ha megnézzük, a kód 8-as shiftelést használ, mivel egy objektum adatait 8 bit tárolja. A mûködési elv ugyanaz, mint az elõzõ encode függvényeknél, így erre nem kívánok megint kitérni.Nézzük azonban hogyan írnánk ehhez egy decode függvényt. Mikor megkérdeztem errõl ismerõsömet, õ azt mondta, ugyanúgy mint az elõzõ, decode_panels függvénynél, csak 8-as shiftelést használunk, és 15 helyett 255-el végezzük a bitenkénti ÉS mûveletet. 255-el? minek?No erre is ki fogok térni késõbb, de haladjunk a szokásos módon. 1 bemeneti paraméterünk van, és 4 kimeneti, vagyis a header rész a következõ: decode_doors(doors, &bonnet, &boot, &driver_door, &passenger_door) Ezzel meg is vagyunk, itt nem látok különösebb magyarázni valót. Én most nem fogok törõdni a 2 nem létezõ ajtóval, ha egy jövõbeni verzióban netán azokat is behoznák mint lekérdezhetõ objektum, akkor ígyis-úgyis módosítani kell majd a kódot, addig meg úgysincs rá szükségünk.Nos, mint az eddigi decode függvényeknél, elsõ feladatunk egy shiftelés mentes bitenkénti ÉS mûvelet elvégzése. Az egyik megoldás a következõ: bonnet = doors & 255; Lehet így is. Azonban véleményem szerint felesleges itt egy ekkora számmal \"szenvednünk\". Miért használjunk ekkora számot, és ezáltal bonyolítsuk feleslegesen a kódunkat, ha nekünk a 8 bitbõl csak az elsõ 3 lesz ami adatot tárol? Bõven elegendõ egy olyan szám, amely bináris alakja 111. Ez nem más, mint a 7. Tehát a következõ megoldás ugyanúgy helyes: bonnet = doors & 7; A két kód pontosan ugyanazt az eredményt fogja visszaadni, semmi különbség a kettõ között. Csupán elsõ esetében nagy számmal dolgozunk, még második verzió esetén csak akkorával, amekkora szükséges.A kód többi részében csak 8-as shifteléssel végigcammogunk a doors változó többi bitjén is: boot = doors >> 8 & 7;driver_door = doors >> 16 & 7;passenger_door = doors >> 24 & 7; A teljes kód egyben: decode_doors(doors, &bonnet, &boot, &driver_door, &passenger_door){ bonnet = doors & 7; boot = doors >> 8 & 7; driver_door = doors >> 16 & 7; passenger_door = doors >> 24 & 7;} A kód pont ugyanazon az elven épül fel, mint az összes többi decode függvényünk, csak épp a benne használatos értékek az \"ajtó\" objectumok adatstruktúrájához vannak igazítva.2.6 Egyéb ötletek gyakorlati alkalmazásra, hasznosításraJoggal felmerülhet a kérdés, hogy ennyi? Emiatt rágtuk végig magunkat ezen néhány tízezer karakteren? Nem. Nem ennyi. A SA:MP-ban rengeteg felhasználási módja lehet még a bitmûveleteknek. Mutatok is néhány dolgot, mire jók még.2.6.1 RGBA->HEX; HEX->RGBA konvertálásokNos, bármily \"hihetetlen\", erre is használható. Miért is? Nos, 16-os számrendszerben 0-9, valamint A-F \"szánjegyek\" léteznek. A hexadecimális számjegy decimális értékben 0-15 lehet. Ha megnézzük bináris értékeket, 0-15 az 4 bittel leírható, hiszen a 15 az bináris alakban 1111. De ha józan paraszti ésszel belegondolunk is. 16 az 24. Így tehát 1 hexadecimális érték, az 4 bináris értékkel írható le. Ebbõl már sejthetõ, hogy itt is shiftelésre lesz szükségünk. Naná Mivel 4 bit ír le egy hexadecimális számjegyet, így 4-esével kell majd shiftelnünk.Mi is az az RGBA?RGBA: A színek olyan meghatározási módja, melyben a 3 alapszín([r]ed, [g]reen, lue) mellett egy 4. érték, az úgynevezett [a]lpha is szerepel, amely az áttetszõséget adja meg. A legtöbb program(pl. paint, PS), és pl. a css(külön függvénnyel!) is elfogadja ezen értékeket decimális számokként(0-255) is, a SA:MP-ban azonban hexadecimális alakban kell megadnunk. Bármily furcsa a következõ 4 sor pont ugyan azt jelenti:\"Normál\" RGB alak(pl. paint, photoshop): Piros: 0, Zöld: 255, Kék: 255CSS alak(függvénnyel): rgba(0, 255, 255, 1);Pawn alak(1): 0x00FFFFFFPawn alak(2): 16777215 Ó igen, a Pawn-ban decimális számokként is megadhatjuk a színeket, hisz a program mindent bináris számrendszerbe fog átszámolni. De akár bináris alakban is megadhattuk volna:Pawn alak(3): 0b111111111111111111111111 Az, hogy mi a Pawn-nak hexadecimális alakban szoktuk megadni, annak az egyetlen oka, hogy így a leginkább \"átlátható\", és ez egy amúgy is elfogadott módja (mármint a hexadecimális számmal való megadás) a színek leírásának a számítástechnikában.Nos, mivel 1 szín 0 és 255 közötti értéket tartalmaz a piros, zöld és kék színekbõl, ez hexadecimálisan 2 számjeggyel leírható: 00-FF. Az FF értéke 255, aki nem hiszi, számoljon utána (162-1)2 hexadecimális számjegy az pedig 8 bináris számjeggyel leírható(00000000-11111111). Így már láthatjuk, hogy 8-as shiftelésre lesz szükségünk. Másra nincs is szükségünk, az RGBA->HEX konverter elkészítéséhez.stock RGBAToHex( r, g, b, a ){return a | b << 8 | g << 16 | r << 24;} Az, hogy miért fordított sorrendben kellett megadnunk az értékeket, az megint csak azzal magyarázható, hogy a biteket hétköznapi értelemben véve visszafelé számozzuk.Egy ellentét, azaz HexToRGBA függvény megírása sem túl bonyolult, ugyan ezt kell csinálnunk, csak visszafelé.stock HexToRGBA( colour, &r, &g, &b, &a ){a = colour & 0xFF;b = (colour >> 8 ) & 0xFF;g = (colour >> 16 ) & 0xFF;r = (colour >> 24 ) & 0xFF;} A 0xFF-et természetesen decimális értékkel is megadhatjuk(255).No akkor lehet ugye RGBA-t ARGB-be is konvertálni, az így néz ki:RGBAToARGBHEX(r,g,b,a){return (r & 0xff) << 24 | (g & 0xff) << 16 | (b & 0xff) << 8 | a & 0xff;} Ez máris egy újabb felhasználási módja a bitmûveleteknek. De, mutatok még 2.6.2 Boolen változók helyett IntegerAz al-al-alcímet elolvasva joggal felmerülhet a kérdés, hogy ez meg hogy jön ide. Nos ez úgy jön ide, hogy most egy kis memóriatakarékosságról fogok itt regélni. Adhattam volna azt is címnek, hogy miért ne használjunk boolean változókat, ha legalább 5 igaz/hamis értéket el akarunk tárolni.Nos akkor egy kis memórialecke. Azt már tisztáztuk, hogy az integer változók 32 bitesek, vagyis 4 bájtot foglalnak le a memóriában maguknak. A boolean típusú változókról azonban még nem volt szó. Mivel 1 értéket tárolnak csak, az is csak igaz vagy hamis lehet, így joggal gondolhatnánk, hogy 1 bit a mérete. Azonban nem...1 boolean változó 8 bitet, azaz 1 bájtot foglal le magának a memóriában. Hogy ez miért így van, az a számítógépek memóriahasználatával magyarázható. Manapság nincs mód arra, hogy a memóriában egyetlen bitet módosítsunk, csak 8-as \"csomagokban\", vagyis bájtonként tudjuk ezt megtenni. Hogy ez miért van így, azt most nem fogom itt fejtegetni Nos, ha megnézzük tehát, a boolean változó egy nagyon-nagyon pazarló megoldás. Hiszen 1 darab boolean változó 1 bájtot foglal, 4 darab boolean változó esetén ez már 4 bájt, ami megegyezik 1 integer változó méretével. Azonban, még a 4 boolean változóban 4 igaz/hamis érték kerülhet tárolásra, addig egy integerben 32 igaz/hamis értéket is eltárolhatunk.Bizony, 32 igaz/hamis érték. Hiszen 1 integer változó 32 bitbõl áll, ha minden bitet hasznosítunk, akkor a bitmûveletek felhasználásával 32 igaz/hamis értéket is eltárolhatunk. Összehasonlítva:32 darab boolean változó mérete 32 bájt.1 integer változó mérete: 4 bájt. Vagyis 32 boolean változó esetén nyolcad annyi memóriánkba kerül, ha egy darab integerben tároljuk el az értékeket. Persze 32 booleanra ritkán van szükség, de egy RP modban simán elõfordulhat. Gondoljunk csak bele... Van 32 boolean-tömbös változónk, ez a maximum slotszámot(1000) figyelembe véve: 32*1000 byte azaz 32000 byte ami ~31 kilobyte. Ha ezt egyetlen integer-tömbbel oldanánk meg: 4*1000 byte, azaz 4000 byte, ami ~3,9 kilobyte. Azért így nézve már nem annyira \"elhanyagolandó\" az a különbség szerintem =)Akár írhatnánk erre külön egy függvényt, mely az adott bit értékével tér vissza. Nem is bonyolult. Így már azok számára is használhatóak a bitmûveletek, akik nem értenek hozzá, és rengeteg memóriát takaríthatnak meg.Íme egy függvény az adatok kiolvasására:stock GetData(integer, bit){ return integer >> bit & 1; //vagy az 1 helyett akár 0x01 alakban is megadhatjuk, nincs jelentõsége} És a párja, adat írásra:stock SetData(&integer, bit, value){ integer |= value << bit; return 1;} Persze ezt még lehetne csinosítani, hogy pl. ne 0-tól hanem 1-tõl számoljuk a biteket, vagy hogy 31(vagy 32 ha 1-tõl számolunk) felett 0 legyen a return értéke, esetleg a value-re is rakhatnánk ellenõrzést, hogy 0-1-e az értéke, vagy más, de ilyenekkel most nem törõdtem.2.6.3 Több érték, egy integerbeNa ez az a rész, ami véleményem szerint már csak a nagyon megrögzött memóriafelszabadítók szerint nem lesz felesleges Amennyiben nincs szükségünk túl nagy számokra(pl. csak 0-15, vagy akár csak 0-7, de akár még a 0-255 is megfelel), akkor akár egy integerbe is tehetjük õket. Hogyan? Egyszerû. Csoportosítjuk a biteket. Tegyük fel, hogy maradván egy RP szervernél, el akarjuk tárolni ûbergigantikusan memóriakímélõ megoldással a játékos házának számát(ez legyen mondjuk 0-100 közötti szám), a játékos frakcióját(ez legyen 0-15 között), a játékos rangját(0-7 között), hogy a telefonja be van-e kapcsolva(0-1), a játékos szolgálati jármûvének id-jét(legyen 0-255 között), és az alapmunkáját(0-15 között). Ez is megoldható, egyetlen integerben, és ismételten 1000 slottal számolva máris megspóroltunk 4*4*1000+1*1000 byte-t(5 integer, és 1 boolean helyett csupán 1 integert használunk), ami ~16,6 kilobyte. Azért nem mindegy az, hogy 20,5 kb-ot vagy 4 kb-ot használunk szerintem... Ezt a következõ módszerrel valósíthatnánk meg:#define DATA_HOUSE 0#define DATA_FACTION 1#define DATA_RANK 2#define DATA_PHONESTATE 3#define DATA_DUTYCAR 4#define DATA_JOB 5stock GetData(playerinfo, data){ switch(data) { case DATA_HOUSE: return playerinfo & 127;//7 bit case DATA_FACTION: return playerinfo >> 7 & 15;//4 bit case DATA_RANK: return playerinfo >> 11 & 7;//3 bit case DATA_PHONESTATE: return playerinfo >> 14 & 1;//1 bit case DATA_DUTYCAR: return playerinfo >> 15 & 255;//8 bit case DATA_JOB: return playerinfo >> 23 & 15;//4 bit}return -1;}stock SetData(&playerinfo, data, value){ switch(data) { case DATA_HOUSE: { playerinfo = playerinfo & ~127 | value; }//7 bit (0-127 közötti maximálisan) case DATA_FACTION: { playerinfo = playerinfo & ~(15 << 7) | value << 7; }//4 bit (0-15 között) case DATA_RANK: { playerinfo = playerinfo & ~(7 << 11) | value << 11; }//3 bit (0-7 között) case DATA_PHONESTATE: { playerinfo = playerinfo & ~(1 << 14) | value << 14; }//1 bit (0-1) case DATA_DUTYCAR: { playerinfo = playerinfo & ~(255 << 15) | value << 15; }//8 bit (0-255 között) case DATA_JOB: { playerinfo = playerinfo & ~(15 << 23) | value << 23; }//4 bit (0-15 között) } return -1;}stock SetDatas(&playerinfo, house, faction, rank, phonestate, dutycar, job){playerinfo |= house | (faction << 7) | (rank << 11) | (phonestate << 14) | (dutycar << 15) | (job << 23);} Ez már azért elég morbid, ezt inkább csak érdekességképp mutattam, de éppenséggel ez is használható lenne, csak ehhez már kell egy kis õrültség 2.7 Második fejezet végeNos, láthattuk hogyan is mûködik mindazon sok \"marhaság\", amit az elsõ fejezetben elméletként átvettünk. Láthattuk hogyan mûködnek a jármûvek paneljének adataival babráló encode függvények, valamint megmutattam, hogyan írhatunk hozzájuk decode verziókat. Láthattunk még példát egyéb gyakorlati hasznosításra, kicsit megkoccoltuk a hexadecimális számok világát is, majd szóba került egy kis memóriatakarékosság. Végül mutattam egy összetettebb, elvetemült példát is, a bitmûveletek hasznosítására.3. Összegzés, végszó Úgy gondolom, hogy elég részletesen körbe jártuk a témát. Megnéztük a bitmûveletek elméleti, és gyakorlati oldalát is, láthattunk gyakorlati példákat. Nem kell elsõre megérteni ezt az egészet, sõt... Elegendõ ha egyszer elolvassuk, emésztjük kicsit, majd késõbb megint elõvesszük, akkor netán már kicsit többet is megértünk belõle. Nem egy kihagyhatatlan része a Pawno-nak, de azért van annak haszna, ha az ember jártas ebben a témában is, bár sokak szerint nem éri meg a sok fáradtságot, és idõt, mely a megértéséhez, begyakorlásához, használatának elsajátításához szükséges. Próbáltam mindent részletesen kifejteni. A bonyolultabb dolgokhoz sokat magyarázni, az egyszerûbbek esetén csak visszautalni az alapokhoz. Sok dolgot talán már túl is magyaráztam. Ezt a leírást nem 1 nap alatt készítettem el, kb. 1 hónapja írtam meg az elméleti oldalát, most az elmúlt pár napban pedig a gyakorlatot. Esténként 1-2 órát szántam rá csupán, és mivel ilyenkor már nem vagyok épp a toppon, elképzelhetõ, hogy néhány helyen helyesírási, vagy elvi hibát vétettem. Amennyiben valaki ilyet találna, kérem szóljon, és korrigálni fogom. Nos, azt hiszem nincs is más hátra, mint hogy sok szerencsét kívánjak a bitmûveletek elsajátításához, és megosszam a forrásokat. Remélem néhányan hasznosnak találják, eme igen hosszúra sikerült leírást.4. Források: Internet:WikipédiaSA:MP WikiSA:MP hivatalos fórumKönyv, e-book:Pohl László - A programozás alapjai(BME - Elektronikus Eszközök Tanszéke, Budapest, 2010)Siroki László - C programozás - Bitmûveletek jegyzeteˆ Anthony, 2012 - Minden jog fenntartva.A leírás egészben, vagy részleteiben szabadon terjeszthetõ, ameddig egyértelmûen fel van tüntetve az eredeti készítõ, valamint hogy honnan is származik. « Utoljára szerkesztve: 2012. augusztus 01. - 10:53:54 írta Anthony » Naplózva
00000000000000000000000000001011|0000000000000000000000000000011100000000000000000000000000001111[/quote]Eredményül tehát 00000000000000000000000000001111-et kaptunk, ami a kettes számrendszerben a 15-nekk felel meg. A következõ 2 sor tehát ugyanazt jelenti: Bin:00000000000000000000000000001011|00000000000000000000000000000111=00000000000000000000000000001111Dec:11|7=15[/quote]Megint néhány példa: 00000000000000000000000000000011 | 00000000000000000000000000000101 = 0000000000000000000000000000011100000000000000000000000001110110 | 00000000000000000000000001011001 = 00000000000000000000000001111111[/quote]1.4.3 Bitenkénti (bitek közötti) KIZÁRÓ VAGY mûveletA bitenkénti KIZÁRÓ VAGY mûvelet (XOR, ^) egy logikai operátor. Ez az operátor nagyon hasonlít a VAGY operátorra, az a fõ különbség a kettõ között, hogy amennyiben mindkét bit 1-es, akkor a KIZÁRÓ VAGY operátor 0-t ad eredményül, a többi esetben ugyanúgy viselkedik mint a VAGY operátor. Igazság táblázata: A B A^B000101011110[/quote]Példának nézzük meg, mi történik, ha a bitenkénti KIZÁRÓ VAGY operátort alkalmazzuk, szintén a 11 és a 7 esetén. A 11 bináris számrendszerben mint már írtam 00000000000000000000000000001011-nek felel meg, a 7 pedig 00000000000000000000000000000111-nek. Tehát a 11 ^ 7, az ugyaz mint ha ezt írnám: 00000000000000000000000000001011 ^ 00000000000000000000000000000111. Az átláthatóság kedvéért ismét egymás alá fogom írni a 2 számot, hogy áttekinthetõbb legyen, és az eredményt pedig a 3. sorba: 00000000000000000000000000001011^0000000000000000000000000000011100000000000000000000000000001100[/quote]Eredményül most 00000000000000000000000000001100-t kaptunk, ami a kettes számrendszerben a 12-nek felel meg. Érdekes, hogy teljesen más eredményt kapunk, pedig lényegében ugyanazt az eredményt adja az operátor, - kis különbséggel ugyebár - mint a VAGY operátor.A következõ 2 sor tehát ugyanazt jelenti: Bin:00000000000000000000000000001011^00000000000000000000000000000111=00000000000000000000000000001100Dec:11^7=12[/quote]Megint néhány példa: 00000000000000000000000000000011 ^ 00000000000000000000000000000101 = 0000000000000000000000000000011000000000000000000000000001110110 ^ 00000000000000000000000001011001 = 00000000000000000000000000101111[/quote]Ezzel kapcsolatban egy érdekességet mutatnék meg. Vegyünk 2 számot. A látványosság kedvéért én példának a 118-at és a 89-et veszem. 1. szám: 00000000000000000000000001110110 = 1182. szám: 00000000000000000000000001011001 = 89Mûvelet elvégzése: 00000000000000000000000001110110 ^ 00000000000000000000000001011001 = 00000000000000000000000000101111 = 471. szám az eredmény, 2. szám az eredeti 2. szám: 00000000000000000000000001011001 = 89Eredmény: 00000000000000000000000000101111 ^ 00000000000000000000000001011001 = 00000000000000000000000001110110 = 118[/quote]Vagyis visszakaptuk a kiindulási állapotot 1.4.4 Bitenkénti (bitek közötti) NEGÁLÁSA bitenkénti NEGÁLÁS (NOT, ~) az utolsó logikai operátor, melyet meg fogok mutatni. Ez az operátor eléggé eltér az eddigiektõl, leginkább abban, hogy ezt a mûveletet nem 2 bitre alkalmazzuk, hanem csak 1-re. Szokás komplementer-operátornak is hívni(konyhanyelven). Lényegében az 1-esekbõl 0-t, a 0-kból 1-est csinál. Igazság táblázata: A ~A0110[/quote]Példának nézzük meg, mi történik, ha a bitenkénti NEGÁLÁST alkalmazzuk a 7-es számra. A 7 bináris számrendszerben mint már írtam 00000000000000000000000000000111-nek felel meg. Tehát a ~7, az ugyaz mint ha ezt írnám: ~00000000000000000000000000000111. Nézzük tehát, mi történik: ~0000000000000000000000000000011111111111111111111111111111111000[/quote]Eredményül most 11111111111111111111111111111000-t kaptunk, ami a kettes számrendszerben a -8-nak felel meg. A következõ 2 sor tehát ugyanazt jelenti: Bin:~00000000000000000000000000000111=11111111111111111111111111111000Dec:~7=-8[/quote]Megint néhány példa: ~00000000000000000000000000000101 = 11111111111111111111111111111011~00000000000000000000000001110110= 11111111111111111111111110001010[/quote]Ehhez kapcsolódóan lenne itt egy fontos megjegyzendõ dolog. Aki nem érti, hogy miért -8 a megoldás, az nem olvasta el elég rendesen a \"Részletesebben a bitekrõl a Pawn-ban\" részt.1.4.5 Biteltolás (arithmetic shift, shiftelés) operátor(ok)A shift operátorok(<<, >>) a bitek tologatására használhatóak. A << balra tol, a >> jobbra. Bal oldalt található a szám, amit tologatni kívánunk, jobb oldalt pedig az, hogy mennyivel szeretnénk eltolni.1.4.5.1 Jobbra toló operátor(>>):Ez az operátor a szám bitjeit jobbra tolja a megadott mértékkel. Egy bittel való eltolás, 2-vel való osztásnak felel meg. Nézzünk egy példát: 00000000000000000000000000001000//8>>200000000000000000000000000000010//2[/quote]Láthatjuk, hogy semmi más nem történt, mint minden bitet 2-vel hátrébb mozgattunk. Az így \"megüresedett\" bitek helyére pedig 0-k kerültek. Lényegében az történt, hogy az utolsó 2 bit kiesett, az így megmaradt biteket 2-vel hátrébb csúsztattuk, majd az elsõ 2, így \"üres\" bit 0 értéket kapott.Nézzünk egy példát negatív számmal: 11111111111111111111111111110000//-8>>211111111111111111111111111111100//-2[/quote]Láthatjuk, hogy itt fontos szerepe van az MSB-nek. Ettõl függ ugyanis, hogy 0-k vagy 1-esek kerülnek a \"megüresedett\" bitek helyére. Fontos tehát megjegyezni, hogy a jobbra shiftelés megtartja a számok elõjelét, nem változtat azon!1.4.5.2 Balra toló operátor(<<):Ez az operátor a szám bitjeit balra tolja a megadott mértékkel. Egy bittel való eltolás, 2-vel való szorzásnak felel meg. Nézzünk egy példát: 00000000000000000000000000001000//8<<200000000000000000000000000100000//32[/quote]Itt megint könnyedén észrevehetõ mi is történt. Minden bitet fentebb \"toltunk\" 2-vel. Azok a bitek, amelyek ezáltal a 32. bit elé kerültek, kiesnek. Az így megüresedett bitek 0-kkal egészültek ki. Eddig nem túl bonyolult. Azonban figyeljünk oda, ha túl sokáig shiftelünk egy pozitív számot, akkor az utolsó érték, melyet fel tud venni a változónk (mielõtt még túlcsordulás miatt elérné a 0-t), az egy negatív szám!Nézzük mi történik negatív számok esetén: 11111111111111111111111111111000//-8<<211111111111111111111111111100000//-32[/quote]Láthatjuk hogy a balra shifteléskor a szám mindig 0-kkal egészítõdik ki, legyen az akár negatív, akár pozitív. Nem veszi figyelembe az MSB értékét, így ha túl sokat shiftelünk, akkor elõfordulhat, hogy a szám elveszti negatív értékét, és eléri a 0-t.1.4.6 Logikai biteltolás (logical shift, logikai shiftelés) operátorElõször is le kell tisztázni, hogy logikai shiftelésbõl csak egy létezik, jobbra logikai shiftelés(>>>). Sokban hasonlít a jobbra shiftelõ operátorra (>>), azonban van egy fontos különbség.Nézzük az elsõ példát: 00000000000000000000000000001000//8>>>200000000000000000000000000000010//2[/quote]Eddig nem igazán tapasztalhattunk semmi változást, minden pont ugyanúgy zajlott, mint a jobbra shiftelésnél(>>).Nézzük mi történik negatív szám esetén: 11111111111111111111111111111000//-8>>>200111111111111111111111111111110 //1073741822[/quote]Itt már láthatjuk a különbséget. Logikai shiftelés esetén az üres bitek helyére bekerülõ érték nem függ az MSB-tõl, hanem mindig 0. Emiatt soha nem kaphatunk eredményül negatív számot, logikai shiftelés esetén.1.5 Elsõ fejezet végeNos, ezennel átvettünk mindent, amit érdemes tudni a bitmûveletekrõl. Megmutattam az alapok, hogy hogyan épülnek fel a változók, valamint átvettük a fõ operátorokat. Eddig csak színtiszta elmélet volt. A következõ részben megmutatom, hogyan hasznosíthatjuk mindezt a gyakorlatban, vagyis mire is jó az a sok marhaság, amit eddig leírtam.2. Gyakorlati példák, érdekességek 2.1 Áttekintés, mikrõl is lesz még szóAz alábbiakban még pár dolgot szeretnék mutatni. Elõször is néhány \"függvénnyel\" fogok foglalkozni, majd mutatok még egy-két érdekességet. Konkrétan megmutatom, hogyan tudjuk egy jármû minden \"paneljének\" adatát kiíratni, részletesen(erre alapból nincs lehetõség, és a függvények is mind \"encode\" függvények, én viszont mutatok egy-egy decode \"verziót\" is.)Az alábbi 4 függvénnyel fogok foglalkozni: encode_tiresencode_panelsencode_doorsencode_lightsDe elõbb egy kis háttérinfó:A Pawnban a forrásban megadhatunk bináris számokat ugyanúgy, ahogyan Hex számokat is megadhatunk. Ehhez a \"0b\" prefixet kell használnunk. Tehát ha bináris számunk az 1011 és ezt akarjuk adni értékül egy változónak, azt így tehetjük meg: new var = 0b1011; 2.2 encode_tiresHáttér információ:A SA:MP-ban egy jármû abroncsainak adataid 4 bit tárolja. Az elsõ bit a jobb hátsó, a második bit a jobb elsõ, a harmadik bit a bal hátsó, a negyedik bit pedig a bal elsõ abroncsról tárol információt. Mivel 4 bitben eltárolhatóak az információk, így decimális formában 0-15 értékekkel is be lehet állítani a jármûvek abroncsait. 16-tól kezdve az értékek már ismétlõdni kezdenek, szóval a 16-nál az eredmény ugyanaz mint 0-nál, 17-nél ugyanaz mind 1-nél, és így tovább. Ez érthetõ, ha tudjuk hogyan épül fel a kettes számrendszer.(Ne felejtsük el, a biteket hátulról számozzuk!)Nos nézzük hogyan néz ki a kód: encode_tires(tire1, tire2, tire3, tire4){ return tire1 | tire2 << 1 | tire3 << 2 | tire4 << 3;} Láthatjuk, hogy bekérjük a 4 abroncs adatát(vagyis hogy ép[0], vagy kidurrant[1]), majd egy sima bitenkénti VAGY mûvelet és biteltolás kombinálásával visszatérünk az eredménnyel. A kódban a tire1 felel meg a jobb hátsó abroncsnak, a tire2 a jobb elsõ, a tire3 a bal hátsó, és a tire4 a bal elsõnek. Nézzünk egy konkrét példát. Tegyük fel, hogy én a hátsó 2 abroncsot ki akarom durrantani, még az elsõ kettõt épre akarom beállítani. Ez esetben így néz ki a dolog: encode_tire(1,0,1,0); Ha kiíratnánk konzolba a visszatérési értéket (%d-vel), 5-öt kapnánk. Miért is? Az 5 bináris alakja 0101. Hoppá, ha megnézzük ez pont fordítottja annak, amit mi megadtunk számjegyekként, csak már konkrét bináris számként. Nézzük hogyan is történhetett mindez, mit is csináltunk. A függvény a következõ mûveletet végzi: tire1 | tire2 << 1 | tire3 << 2 | tire4 << 3 Konyhanyelven úgy magyarázhatnánk el a dolgot, hogy ez a kód egymás mellé pakolgatja a megadott számokat, fordított sorrendben. Na de mi most nem a konyhanyelv miatt vagyunk ebben a leírásban, szóval nézzük a kódunkat. Lényegében ez futott le, a konkrét értékeinkkel nézve: 1 | 0 << 1 | 1 << 2 | 0 << 3 Bármily furcsa is, ez egy mûvelet, melyen szépen végig halad sorban a program. Elõször is írjuk át az egészet bináris alakba(az egyszerûség kedvéért most csak 4 bitet fogok kiírni): 0001 | 0000 << 1 | 0001 << 2 | 0000 << 3 Na erre se mondtuk volna rá a tutorial elsõ részének elolvasása elõtt, hogy ez egy mûveletsor. A rendszer elsõnek az eltolásokat végzi el (olyan ez mint a 2*3+2*4+2*5 mûveletben elõször a szorzásokat végezzük el, és csak utána adunk össze, ha nincs zárójel), és csak utána a bitenkénti VAGY mûveleteket, ezért mi is így fogunk tenni. Emlékezzünk, eltoláskor az \"üres\" bitek 0 értéket fognak felvenni. Végezzük el külön csak az eltolásokat: 0000 << 1 = 00000001 << 2 = 01000000 << 3 0000 Nos, ha ezeket elvégeztük, akkor nézzük, hogy is áll most a kódunk: 0001 | 0000 | 0100 | 0000 Négy azonos mûvelet van hátra, így hát a program innen más szépen sorban fog haladni. Tegyünk mi is így. Elsõ tehát a 0001 | 0000 mûvelet elvégzése. Emlékeztetõül, a bitenkénti VAGY mûvelet akkor ad 0-t eredményül, ha mind a kettõ érték 0, minden más esetben 1-et. Végezzük el külön az elsõ bitenkénti VAGY mûveletet: 0001 | 0000 = 0001 Nem volt bonyolult, nézzük tehát hogy áll akkor most a teljes mûveletünk: 0001 | 0100 | 0000 Haladunk szépen tovább, elvégezzük a 0001 | 0100 mûveletet. 0001 | 0100 = 0101 Nincs mit magyarázni rajta, egyszerû bitenkénti VAGY mûvelet volt ez is. Már csak ez van hátra: 0101 | 0000 Nos, ez sem lesz egy bonyolult mûvelet: 0101 | 0000 = 0101 Ezzel meg is kaptuk a végeredményt, ami nem más, mint a 0101 (tízes számrendszerben 5). Mivel azonban eléggé kusza lett így a számításunk, nézzük egyben hogyan is néz ki az egész, ha nem bontom ennyire részekre: 0100 | 0000 << 1 | 0001 << 2 | 0000 << 3v0001 | 0000 | 0100 | 0000v0001 | 0100 | 0000v0101 | 0000v0101 Nos, ezennel láthattuk a gyakorlatban is, hogyan mûködik a függvény. Kijelenthetjük tehát, hogy a függvény a számokat a sorrendjüknek megfelelõ számú bitbe helyezi. Mivel azonban a biteket hátulról számozzuk, így az 1,0,1,0 bemenetek végeredménye 0101 lesz. Így már nem is olyan bonyolult Na, ha már ilyen sokáig eljutottunk, akkor felhasználhatjuk ezt a kódot arra, hogy az alapjait figyelembe véve elkészítsük a decode változatot. Mivel 4 értékkel térünk vissza, ezért paraméterként kell majd 4 változó, melyeknek módosíthatjuk az értékét, illetve 1 paraméter pedig a dekódolandó szám. A függvényünk \"címsora\" tehát így néz ki: decode_tires(tires, &tire1, &tire2, &tire3, &tire4) Hogy példánk ne csak elmélet legyen, a tires változó legyen az elõbbi 0101 értékkel egyenlõ. Na akkor menjünk tovább. Bemeneti értékünk ugye a \"tires\" változóban lesz eltárolva, így ezzel fogjuk a mûveleteket végezni. Kezdjünk el gondolkozni. Hogyan is tudnánk ellenõrizni egy adatot. Legegyszerûbb ha a bitenkénti ÉS mûveletet alkalmazzuk. Miért is? Nos, a bitenkénti ÉS mûvelet ugye akkor ad eredményül 1-et, ha mind a 2 érték egy. Tehát ha min egy számot összehasonlítunk a 1-el(binárisan ugye 00000000000000000000000000000001, akkor az eredményünk lényegében arról fog tájékoztatni, hogy az elsõ bit 0, vagy 1. Nem is olyan bonyolult ez. Nézzük akkor az elsõ bitet. Arra emlékezhetünk még, hogy az elsõ bit a tire1, vagyis jobb hátsó abroncs adatait tárolja. Tehát: tire1 = tires & 1; Vagy konkrétan a példánk esetében: tire1 = 0101 & 0001 Mivel a bitenkénti ÉS mûvelet akkor ad 1-et eredményül, ha mind a kettõ bit 1, akkor már láthatjuk is, hogy miért elegendõ az elsõ bitet szemügyre venni. Mivel mind a 2 helyen az elsõ bit 1, ezért a tire1 változónk értéke is 1(0001) lesz. Egyszerû és nagyszerû, máris megkaptuk a jobb hátsó abroncs állapotát.Gondolkozzunk hát tovább. Nekünk most arra van szükségünk, hogy a 2. bit értékét megkapjuk. Hogyan kaphatjuk ezt meg? Legegyszerûbb megoldás, ha a tires változót jobbra shifteljük 1-el, majd ismételten bitenkénti ÉS mûveletet végzünk. Nézzük tehát, hogyan is nézne ez ki: tire2= tires >> 1 & 1; Vagy konkrétan a mi példánkban: tire2 = 0101 >> 1 & 0001vtire2 = 0010 & 0001 Ismételten bitenkénti ÉS mûvelet, és az eredményünk 0, mivel csak az egyik bit 1, a másik 0, így a bitenkénti ÉS mûvelet eredménye is 0. Ezen az elven megcsináljuk a maradék 2 abronccsal is: tire3= tires >> 2 & 1;tire4= tires >> 3 & 1; Vagy a mi esetünkben konkrét példánkkal: tire3 = 0101 >> 2 & 0001 // azaz: --> 0001 & 0001 = 1tire4 = 0101 >> 3 & 0001 // azaz: --> 0000 & 0001 = 0 A teljes kódunk tehát: decode_tires(tires, &tire1, &tire2, &tire3, &tire4){ tire1 = tires & 1; tire2 = tires >> 1 & 1; tire3 = tires >> 2 & 1; tire4 = tires >> 3 & 1;} A konkrét példánkban a 4 változónk értéke a függvény lefutása után: tire1: 1tire2: 0tire3: 1tire4: 0 Ha összehasonlítjuk, ezek pont azok az értékek, mint amiket az encode függvényben megadtunk, tehát úgy tûnik jól dolgoztunk 2.3 encode_lightsEz a rész lesz a legrövidebb. Az egész lámpás rész pontosan ugyanarra a sémára épül mint az abroncsok. Az adott bitek ugyanazon a \"helyen\" lévõ lámpa adatait szolgáltatják, ugyanúgy 0 az ép, és 1 a sérült lámpák állapot azonosítója, és ugyanúgy 1-1 bit tárolja az értékeket.Maga a kód: encode_lights(light1, light2, light3, light4){ return light1 | light2 << 1 | light3 << 2 | light4 << 3;} Láthatjuk hogy halál pontosan ugyanaz mint az encode_tires, csak más változónevekkel. Így hát a decode függvény is ugyan az lesz, csak a változóneveket kell módosítanunk: decode_lights(lights, &light1, &light2, &light3, &light4){ light1 = lights & 1; light2 = lights >> 1 & 1; light3 = lights >> 2 & 1; light4 = lights >> 3 & 1;} Ha megnézzük látjuk, hogy teljesen ugyan az a séma, ugyan az a felépítés, minden megegyezik, ezért nem is írtam le újból ugyanazt, amit egyszer már kifejtettem.2.4 encode_panelsHáttér információ:A SA:MP 7 panelrõl tárol adatokat. Egy panel adatait 4 bit írja le. Nos, ennél több infóra nekünk most nincs szükségünk, az elõzõ kód alapján ezen kód is megérthetõ, így az encode verzióhoz nem is nagyon szeretnék magyarázatot fûzni. A különbség az encode_tires-hez képest, hogy itt 4-essével shiftelünk, mivel 1 panel adatait 4 bit tárolja.Maga a kód: encode_panels(flp, frp, rlp, rrp, windshield, front_bumper, rear_bumper){ return flp | frp << 4 | rlp << 8 | rrp << 12 | windshield << 16 | front_bumper << 20 | rear_bumper << 24;} Nézzünk egy decode verziót, ehhez a kódhoz. Alapjaiban ugyanazon a vonalon kell elindulnunk, mint a decode_tires esetében. Itt most 7 visszatérési értékünk lesz, és 1 bemeneti érték. Tehát így néz ki a függvény header: decode_panels(panels, &flp, &frp, &rlp, &rrp, &windshield, &front_bumper, &rear_bumper) A folytatásban megint csak az elõbbi verziót kell alapul vennünk, azonban itt lesz egy kis különbség. Egyértelmû, hogy mivel 1 panel adatait 4 bit tárolja, így a 4 bitet nekünk egyben kell lekérni, és egyben kell vele visszatérnünk. Megint csak a bitenkénti ÉS mûveletet fogjuk alkalmazni, azonban mivel 4 bitrõl van szó, így egy olyan számmal kell összehasonlítanunk, mely bináris alakban 1111. Ez a szám nem más, mint a 15. Hogy miért ez kell?Ha egy bitsorozat tartalmát ki szeretnénk másolni egy másik változóba, akkor a bitenkénti ÉS mûveletre vna szükségünk. A bitenkénti ÉS mûvelet akkor ad 1-et eredményül, ha mindkét bit 1, ha valamelyik 0, akkor a visszatérés már 0 lesz. Ergó tehát, ha egy csupa 1-esekbõl álló bitsorozattal vetjük össze az eredeti bitsorozatunkat, akkor lényegében lemásoljuk a sorozatot, egy másik változóba, hisz eredményül mindig az eredeti sozatot fogjuk visszakapni. Íme néhány példa: 0101 & 1111 = 01011111 & 1111 = 11110000 & 1111 = 00001011 & 1111 = 1011 Láthatjuk tehát, hogy ha csupa 1-est tartalmazó bitsorozattal hasonlítjuk össze az eredeti számunkat, akkor lényegében egy másolatot készítünk. Mivel nekünk erre a 4 bitre van szükségünk a 32-bõl, így pontosan ezt kell tennünk, ezen 4 bitet lemásolni egy másik változóba, hogy késõbb ezt majd egy másik függvénnyel még jobban darabokra szedhessük.Íme tehát akkor a kódunk elsõ sora: flp = panels & 15; Mivel 4-esével vizsgáljuk a biteket, így lényegében annyit kell még változtatnunk az elõzõ kódon(decode_tires), hogy az 1-esével növekvõ shiftelések helyett 4-esével shifteljünk. Azaz a 2. panel adatainak lekérdezése: frp = panels >> 4 & 15; Innen pedig már csak mindig további 4 shiftelést el kell végeznünk, és szépen végig haladhatunk a panels változó bitjein, és megkaphatjuk minden panel adatait külön-külön. rlp = panels >> 8 & 15;rrp = panels >> 12 & 15;windshield = panels >> 16 & 15;front_bumper= panels >> 20 & 15;rear_bumper = panels >> 24 & 15; Ezennel minden panel adatait megkaptuk. Ezután már akár a decode_tires függvényt felhasználva szét lehet szedni az adatokat darabokra, de akár egy saját névvel, és más változó nevekkel ellátott másik függvényt is létre lehet hozni a decode_tires mintájára.Tehát az egész kód egyben: decode_panels(panels, &flp, &frp, &rlp, &rrp, &windshield, &front_bumper, &rear_bumper){ flp = panels & 15; frp = panels >> 4 & 15; rlp = panels >> 8 & 15; rrp = panels >> 12 & 15; windshield = panels >> 16 & 15; front_bumper= panels >> 20 & 15; rear_bumper = panels >> 24 & 15;} Igazából a függvény ugyanarra a sémára épül mint a decode_tires, csak annyi a különbség, hogy 4 bitenként kapjuk az adatokat, nem pedig egyesével, mivel itt minden panelnek 4 adatát tudjuk majd még lekérdezni. Nem tudom lehet-e még ehhez a részhez hozzáfûzni valamit, szóval szerintem ugorjunk. 2.5 encode_doorsHáttér információ:A SA:MP képes minden jármû 4 \"ajtajának\" állapotát tárolni. Ezek az \"ajtók\"(Doors-t hogy fordíthatnám jobban?) konkrétan a motorháztetõ, csomagtartó, vezetõoldali ajtó, és anyósülés oldali ajtó. A 2 hátsó ajtó állapotát jelenleg még nincs lehetõségünk lekérdezni.Amikor lekérdezzük egy jármû \"ajtajának\" adatait, azt a SA:MP 4 bájtként(32 bit) adja vissza. Ebbõl a 4 bájtból 1 bájt(8 bit) jut minden egyes objektumra. Vagyis minden egyes objektum adatait 8 bit tárolja. Vagyis tárolhatná...A SA:MP csak az elsõ 3 bitet használja, a további bitek üresek lesznek, nincs funkciójuk, talán majd késõbb lesz. Az elsõ bit azt tárolja, hogy az objektum zárt[0], vagy nyitott[1](nem az jelenti, hogy bezárt, hanem becsukott/kinyitott) állapotban van-e. A második bit azt adja meg, hogy az objektum ép[0] vagy sérült[1]. A 3. bit arról tájékoztat, hogy az adott objektum rajta van-e még a jármûvön[0] vagy már leesett[1]. A többi bit mint mondtam üres. Ez majd a decode függvénynél lesz fontos.Nézzük tehát a konkrét kódot: encode_doors(bonnet, boot, driver_door, passenger_door, behind_driver_door, behind_passenger_door){ #pragma unused behind_driver_door #pragma unused behind_passenger_door return bonnet | boot << 8 | driver_door << 16 | passenger_door << 24;} A 2 hátsó ajtóval nem foglalkozunk, mivel azok jelenleg még nem támogatottak ugyebár, de a kód készítõje gondolt a jövõre is, vagy fogalmam sincs miért adta meg ezt a két paramétert is, hisz ígyis-úgyis módosítani kell a kódon... Ha behoznak még 2 ajtót, akkor az 1 bájtos tárolással már 6 bájt lenne, de 1 változó maximum 4 bájt lehet, tehát a kódot úgyis módosítani kell, ha meg arra gondoltak, hogy legalább a paramétereket nem kell majd szegény scriptereknek minden sorban átírni, az megint hülyeség, hisz ki adna értéket olyan paraméternek, amely nincs használva?...Nos mindegy, nem a kritizálásért vagyunk most itt. Ha megnézzük, a kód 8-as shiftelést használ, mivel egy objektum adatait 8 bit tárolja. A mûködési elv ugyanaz, mint az elõzõ encode függvényeknél, így erre nem kívánok megint kitérni.Nézzük azonban hogyan írnánk ehhez egy decode függvényt. Mikor megkérdeztem errõl ismerõsömet, õ azt mondta, ugyanúgy mint az elõzõ, decode_panels függvénynél, csak 8-as shiftelést használunk, és 15 helyett 255-el végezzük a bitenkénti ÉS mûveletet. 255-el? minek?No erre is ki fogok térni késõbb, de haladjunk a szokásos módon. 1 bemeneti paraméterünk van, és 4 kimeneti, vagyis a header rész a következõ: decode_doors(doors, &bonnet, &boot, &driver_door, &passenger_door) Ezzel meg is vagyunk, itt nem látok különösebb magyarázni valót. Én most nem fogok törõdni a 2 nem létezõ ajtóval, ha egy jövõbeni verzióban netán azokat is behoznák mint lekérdezhetõ objektum, akkor ígyis-úgyis módosítani kell majd a kódot, addig meg úgysincs rá szükségünk.Nos, mint az eddigi decode függvényeknél, elsõ feladatunk egy shiftelés mentes bitenkénti ÉS mûvelet elvégzése. Az egyik megoldás a következõ: bonnet = doors & 255; Lehet így is. Azonban véleményem szerint felesleges itt egy ekkora számmal \"szenvednünk\". Miért használjunk ekkora számot, és ezáltal bonyolítsuk feleslegesen a kódunkat, ha nekünk a 8 bitbõl csak az elsõ 3 lesz ami adatot tárol? Bõven elegendõ egy olyan szám, amely bináris alakja 111. Ez nem más, mint a 7. Tehát a következõ megoldás ugyanúgy helyes: bonnet = doors & 7; A két kód pontosan ugyanazt az eredményt fogja visszaadni, semmi különbség a kettõ között. Csupán elsõ esetében nagy számmal dolgozunk, még második verzió esetén csak akkorával, amekkora szükséges.A kód többi részében csak 8-as shifteléssel végigcammogunk a doors változó többi bitjén is: boot = doors >> 8 & 7;driver_door = doors >> 16 & 7;passenger_door = doors >> 24 & 7; A teljes kód egyben: decode_doors(doors, &bonnet, &boot, &driver_door, &passenger_door){ bonnet = doors & 7; boot = doors >> 8 & 7; driver_door = doors >> 16 & 7; passenger_door = doors >> 24 & 7;} A kód pont ugyanazon az elven épül fel, mint az összes többi decode függvényünk, csak épp a benne használatos értékek az \"ajtó\" objectumok adatstruktúrájához vannak igazítva.2.6 Egyéb ötletek gyakorlati alkalmazásra, hasznosításraJoggal felmerülhet a kérdés, hogy ennyi? Emiatt rágtuk végig magunkat ezen néhány tízezer karakteren? Nem. Nem ennyi. A SA:MP-ban rengeteg felhasználási módja lehet még a bitmûveleteknek. Mutatok is néhány dolgot, mire jók még.2.6.1 RGBA->HEX; HEX->RGBA konvertálásokNos, bármily \"hihetetlen\", erre is használható. Miért is? Nos, 16-os számrendszerben 0-9, valamint A-F \"szánjegyek\" léteznek. A hexadecimális számjegy decimális értékben 0-15 lehet. Ha megnézzük bináris értékeket, 0-15 az 4 bittel leírható, hiszen a 15 az bináris alakban 1111. De ha józan paraszti ésszel belegondolunk is. 16 az 24. Így tehát 1 hexadecimális érték, az 4 bináris értékkel írható le. Ebbõl már sejthetõ, hogy itt is shiftelésre lesz szükségünk. Naná Mivel 4 bit ír le egy hexadecimális számjegyet, így 4-esével kell majd shiftelnünk.Mi is az az RGBA?RGBA: A színek olyan meghatározási módja, melyben a 3 alapszín([r]ed, [g]reen, lue) mellett egy 4. érték, az úgynevezett [a]lpha is szerepel, amely az áttetszõséget adja meg. A legtöbb program(pl. paint, PS), és pl. a css(külön függvénnyel!) is elfogadja ezen értékeket decimális számokként(0-255) is, a SA:MP-ban azonban hexadecimális alakban kell megadnunk. Bármily furcsa a következõ 4 sor pont ugyan azt jelenti:\"Normál\" RGB alak(pl. paint, photoshop): Piros: 0, Zöld: 255, Kék: 255CSS alak(függvénnyel): rgba(0, 255, 255, 1);Pawn alak(1): 0x00FFFFFFPawn alak(2): 16777215 Ó igen, a Pawn-ban decimális számokként is megadhatjuk a színeket, hisz a program mindent bináris számrendszerbe fog átszámolni. De akár bináris alakban is megadhattuk volna:Pawn alak(3): 0b111111111111111111111111 Az, hogy mi a Pawn-nak hexadecimális alakban szoktuk megadni, annak az egyetlen oka, hogy így a leginkább \"átlátható\", és ez egy amúgy is elfogadott módja (mármint a hexadecimális számmal való megadás) a színek leírásának a számítástechnikában.Nos, mivel 1 szín 0 és 255 közötti értéket tartalmaz a piros, zöld és kék színekbõl, ez hexadecimálisan 2 számjeggyel leírható: 00-FF. Az FF értéke 255, aki nem hiszi, számoljon utána (162-1)2 hexadecimális számjegy az pedig 8 bináris számjeggyel leírható(00000000-11111111). Így már láthatjuk, hogy 8-as shiftelésre lesz szükségünk. Másra nincs is szükségünk, az RGBA->HEX konverter elkészítéséhez.stock RGBAToHex( r, g, b, a ){return a | b << 8 | g << 16 | r << 24;} Az, hogy miért fordított sorrendben kellett megadnunk az értékeket, az megint csak azzal magyarázható, hogy a biteket hétköznapi értelemben véve visszafelé számozzuk.Egy ellentét, azaz HexToRGBA függvény megírása sem túl bonyolult, ugyan ezt kell csinálnunk, csak visszafelé.stock HexToRGBA( colour, &r, &g, &b, &a ){a = colour & 0xFF;b = (colour >> 8 ) & 0xFF;g = (colour >> 16 ) & 0xFF;r = (colour >> 24 ) & 0xFF;} A 0xFF-et természetesen decimális értékkel is megadhatjuk(255).No akkor lehet ugye RGBA-t ARGB-be is konvertálni, az így néz ki:RGBAToARGBHEX(r,g,b,a){return (r & 0xff) << 24 | (g & 0xff) << 16 | (b & 0xff) << 8 | a & 0xff;} Ez máris egy újabb felhasználási módja a bitmûveleteknek. De, mutatok még 2.6.2 Boolen változók helyett IntegerAz al-al-alcímet elolvasva joggal felmerülhet a kérdés, hogy ez meg hogy jön ide. Nos ez úgy jön ide, hogy most egy kis memóriatakarékosságról fogok itt regélni. Adhattam volna azt is címnek, hogy miért ne használjunk boolean változókat, ha legalább 5 igaz/hamis értéket el akarunk tárolni.Nos akkor egy kis memórialecke. Azt már tisztáztuk, hogy az integer változók 32 bitesek, vagyis 4 bájtot foglalnak le a memóriában maguknak. A boolean típusú változókról azonban még nem volt szó. Mivel 1 értéket tárolnak csak, az is csak igaz vagy hamis lehet, így joggal gondolhatnánk, hogy 1 bit a mérete. Azonban nem...1 boolean változó 8 bitet, azaz 1 bájtot foglal le magának a memóriában. Hogy ez miért így van, az a számítógépek memóriahasználatával magyarázható. Manapság nincs mód arra, hogy a memóriában egyetlen bitet módosítsunk, csak 8-as \"csomagokban\", vagyis bájtonként tudjuk ezt megtenni. Hogy ez miért van így, azt most nem fogom itt fejtegetni Nos, ha megnézzük tehát, a boolean változó egy nagyon-nagyon pazarló megoldás. Hiszen 1 darab boolean változó 1 bájtot foglal, 4 darab boolean változó esetén ez már 4 bájt, ami megegyezik 1 integer változó méretével. Azonban, még a 4 boolean változóban 4 igaz/hamis érték kerülhet tárolásra, addig egy integerben 32 igaz/hamis értéket is eltárolhatunk.Bizony, 32 igaz/hamis érték. Hiszen 1 integer változó 32 bitbõl áll, ha minden bitet hasznosítunk, akkor a bitmûveletek felhasználásával 32 igaz/hamis értéket is eltárolhatunk. Összehasonlítva:32 darab boolean változó mérete 32 bájt.1 integer változó mérete: 4 bájt. Vagyis 32 boolean változó esetén nyolcad annyi memóriánkba kerül, ha egy darab integerben tároljuk el az értékeket. Persze 32 booleanra ritkán van szükség, de egy RP modban simán elõfordulhat. Gondoljunk csak bele... Van 32 boolean-tömbös változónk, ez a maximum slotszámot(1000) figyelembe véve: 32*1000 byte azaz 32000 byte ami ~31 kilobyte. Ha ezt egyetlen integer-tömbbel oldanánk meg: 4*1000 byte, azaz 4000 byte, ami ~3,9 kilobyte. Azért így nézve már nem annyira \"elhanyagolandó\" az a különbség szerintem =)Akár írhatnánk erre külön egy függvényt, mely az adott bit értékével tér vissza. Nem is bonyolult. Így már azok számára is használhatóak a bitmûveletek, akik nem értenek hozzá, és rengeteg memóriát takaríthatnak meg.Íme egy függvény az adatok kiolvasására:stock GetData(integer, bit){ return integer >> bit & 1; //vagy az 1 helyett akár 0x01 alakban is megadhatjuk, nincs jelentõsége} És a párja, adat írásra:stock SetData(&integer, bit, value){ integer |= value << bit; return 1;} Persze ezt még lehetne csinosítani, hogy pl. ne 0-tól hanem 1-tõl számoljuk a biteket, vagy hogy 31(vagy 32 ha 1-tõl számolunk) felett 0 legyen a return értéke, esetleg a value-re is rakhatnánk ellenõrzést, hogy 0-1-e az értéke, vagy más, de ilyenekkel most nem törõdtem.2.6.3 Több érték, egy integerbeNa ez az a rész, ami véleményem szerint már csak a nagyon megrögzött memóriafelszabadítók szerint nem lesz felesleges Amennyiben nincs szükségünk túl nagy számokra(pl. csak 0-15, vagy akár csak 0-7, de akár még a 0-255 is megfelel), akkor akár egy integerbe is tehetjük õket. Hogyan? Egyszerû. Csoportosítjuk a biteket. Tegyük fel, hogy maradván egy RP szervernél, el akarjuk tárolni ûbergigantikusan memóriakímélõ megoldással a játékos házának számát(ez legyen mondjuk 0-100 közötti szám), a játékos frakcióját(ez legyen 0-15 között), a játékos rangját(0-7 között), hogy a telefonja be van-e kapcsolva(0-1), a játékos szolgálati jármûvének id-jét(legyen 0-255 között), és az alapmunkáját(0-15 között). Ez is megoldható, egyetlen integerben, és ismételten 1000 slottal számolva máris megspóroltunk 4*4*1000+1*1000 byte-t(5 integer, és 1 boolean helyett csupán 1 integert használunk), ami ~16,6 kilobyte. Azért nem mindegy az, hogy 20,5 kb-ot vagy 4 kb-ot használunk szerintem... Ezt a következõ módszerrel valósíthatnánk meg:#define DATA_HOUSE 0#define DATA_FACTION 1#define DATA_RANK 2#define DATA_PHONESTATE 3#define DATA_DUTYCAR 4#define DATA_JOB 5stock GetData(playerinfo, data){ switch(data) { case DATA_HOUSE: return playerinfo & 127;//7 bit case DATA_FACTION: return playerinfo >> 7 & 15;//4 bit case DATA_RANK: return playerinfo >> 11 & 7;//3 bit case DATA_PHONESTATE: return playerinfo >> 14 & 1;//1 bit case DATA_DUTYCAR: return playerinfo >> 15 & 255;//8 bit case DATA_JOB: return playerinfo >> 23 & 15;//4 bit}return -1;}stock SetData(&playerinfo, data, value){ switch(data) { case DATA_HOUSE: { playerinfo = playerinfo & ~127 | value; }//7 bit (0-127 közötti maximálisan) case DATA_FACTION: { playerinfo = playerinfo & ~(15 << 7) | value << 7; }//4 bit (0-15 között) case DATA_RANK: { playerinfo = playerinfo & ~(7 << 11) | value << 11; }//3 bit (0-7 között) case DATA_PHONESTATE: { playerinfo = playerinfo & ~(1 << 14) | value << 14; }//1 bit (0-1) case DATA_DUTYCAR: { playerinfo = playerinfo & ~(255 << 15) | value << 15; }//8 bit (0-255 között) case DATA_JOB: { playerinfo = playerinfo & ~(15 << 23) | value << 23; }//4 bit (0-15 között) } return -1;}stock SetDatas(&playerinfo, house, faction, rank, phonestate, dutycar, job){playerinfo |= house | (faction << 7) | (rank << 11) | (phonestate << 14) | (dutycar << 15) | (job << 23);} Ez már azért elég morbid, ezt inkább csak érdekességképp mutattam, de éppenséggel ez is használható lenne, csak ehhez már kell egy kis õrültség 2.7 Második fejezet végeNos, láthattuk hogyan is mûködik mindazon sok \"marhaság\", amit az elsõ fejezetben elméletként átvettünk. Láthattuk hogyan mûködnek a jármûvek paneljének adataival babráló encode függvények, valamint megmutattam, hogyan írhatunk hozzájuk decode verziókat. Láthattunk még példát egyéb gyakorlati hasznosításra, kicsit megkoccoltuk a hexadecimális számok világát is, majd szóba került egy kis memóriatakarékosság. Végül mutattam egy összetettebb, elvetemült példát is, a bitmûveletek hasznosítására.3. Összegzés, végszó Úgy gondolom, hogy elég részletesen körbe jártuk a témát. Megnéztük a bitmûveletek elméleti, és gyakorlati oldalát is, láthattunk gyakorlati példákat. Nem kell elsõre megérteni ezt az egészet, sõt... Elegendõ ha egyszer elolvassuk, emésztjük kicsit, majd késõbb megint elõvesszük, akkor netán már kicsit többet is megértünk belõle. Nem egy kihagyhatatlan része a Pawno-nak, de azért van annak haszna, ha az ember jártas ebben a témában is, bár sokak szerint nem éri meg a sok fáradtságot, és idõt, mely a megértéséhez, begyakorlásához, használatának elsajátításához szükséges. Próbáltam mindent részletesen kifejteni. A bonyolultabb dolgokhoz sokat magyarázni, az egyszerûbbek esetén csak visszautalni az alapokhoz. Sok dolgot talán már túl is magyaráztam. Ezt a leírást nem 1 nap alatt készítettem el, kb. 1 hónapja írtam meg az elméleti oldalát, most az elmúlt pár napban pedig a gyakorlatot. Esténként 1-2 órát szántam rá csupán, és mivel ilyenkor már nem vagyok épp a toppon, elképzelhetõ, hogy néhány helyen helyesírási, vagy elvi hibát vétettem. Amennyiben valaki ilyet találna, kérem szóljon, és korrigálni fogom. Nos, azt hiszem nincs is más hátra, mint hogy sok szerencsét kívánjak a bitmûveletek elsajátításához, és megosszam a forrásokat. Remélem néhányan hasznosnak találják, eme igen hosszúra sikerült leírást.4. Források: Internet:WikipédiaSA:MP WikiSA:MP hivatalos fórumKönyv, e-book:Pohl László - A programozás alapjai(BME - Elektronikus Eszközök Tanszéke, Budapest, 2010)Siroki László - C programozás - Bitmûveletek jegyzeteˆ Anthony, 2012 - Minden jog fenntartva.A leírás egészben, vagy részleteiben szabadon terjeszthetõ, ameddig egyértelmûen fel van tüntetve az eredeti készítõ, valamint hogy honnan is származik. « Utoljára szerkesztve: 2012. augusztus 01. - 10:53:54 írta Anthony » Naplózva
Bin:00000000000000000000000000001011|00000000000000000000000000000111=00000000000000000000000000001111Dec:11|7=15[/quote]Megint néhány példa: 00000000000000000000000000000011 | 00000000000000000000000000000101 = 0000000000000000000000000000011100000000000000000000000001110110 | 00000000000000000000000001011001 = 00000000000000000000000001111111[/quote]1.4.3 Bitenkénti (bitek közötti) KIZÁRÓ VAGY mûveletA bitenkénti KIZÁRÓ VAGY mûvelet (XOR, ^) egy logikai operátor. Ez az operátor nagyon hasonlít a VAGY operátorra, az a fõ különbség a kettõ között, hogy amennyiben mindkét bit 1-es, akkor a KIZÁRÓ VAGY operátor 0-t ad eredményül, a többi esetben ugyanúgy viselkedik mint a VAGY operátor. Igazság táblázata: A B A^B000101011110[/quote]Példának nézzük meg, mi történik, ha a bitenkénti KIZÁRÓ VAGY operátort alkalmazzuk, szintén a 11 és a 7 esetén. A 11 bináris számrendszerben mint már írtam 00000000000000000000000000001011-nek felel meg, a 7 pedig 00000000000000000000000000000111-nek. Tehát a 11 ^ 7, az ugyaz mint ha ezt írnám: 00000000000000000000000000001011 ^ 00000000000000000000000000000111. Az átláthatóság kedvéért ismét egymás alá fogom írni a 2 számot, hogy áttekinthetõbb legyen, és az eredményt pedig a 3. sorba: 00000000000000000000000000001011^0000000000000000000000000000011100000000000000000000000000001100[/quote]Eredményül most 00000000000000000000000000001100-t kaptunk, ami a kettes számrendszerben a 12-nek felel meg. Érdekes, hogy teljesen más eredményt kapunk, pedig lényegében ugyanazt az eredményt adja az operátor, - kis különbséggel ugyebár - mint a VAGY operátor.A következõ 2 sor tehát ugyanazt jelenti: Bin:00000000000000000000000000001011^00000000000000000000000000000111=00000000000000000000000000001100Dec:11^7=12[/quote]Megint néhány példa: 00000000000000000000000000000011 ^ 00000000000000000000000000000101 = 0000000000000000000000000000011000000000000000000000000001110110 ^ 00000000000000000000000001011001 = 00000000000000000000000000101111[/quote]Ezzel kapcsolatban egy érdekességet mutatnék meg. Vegyünk 2 számot. A látványosság kedvéért én példának a 118-at és a 89-et veszem. 1. szám: 00000000000000000000000001110110 = 1182. szám: 00000000000000000000000001011001 = 89Mûvelet elvégzése: 00000000000000000000000001110110 ^ 00000000000000000000000001011001 = 00000000000000000000000000101111 = 471. szám az eredmény, 2. szám az eredeti 2. szám: 00000000000000000000000001011001 = 89Eredmény: 00000000000000000000000000101111 ^ 00000000000000000000000001011001 = 00000000000000000000000001110110 = 118[/quote]Vagyis visszakaptuk a kiindulási állapotot 1.4.4 Bitenkénti (bitek közötti) NEGÁLÁSA bitenkénti NEGÁLÁS (NOT, ~) az utolsó logikai operátor, melyet meg fogok mutatni. Ez az operátor eléggé eltér az eddigiektõl, leginkább abban, hogy ezt a mûveletet nem 2 bitre alkalmazzuk, hanem csak 1-re. Szokás komplementer-operátornak is hívni(konyhanyelven). Lényegében az 1-esekbõl 0-t, a 0-kból 1-est csinál. Igazság táblázata: A ~A0110[/quote]Példának nézzük meg, mi történik, ha a bitenkénti NEGÁLÁST alkalmazzuk a 7-es számra. A 7 bináris számrendszerben mint már írtam 00000000000000000000000000000111-nek felel meg. Tehát a ~7, az ugyaz mint ha ezt írnám: ~00000000000000000000000000000111. Nézzük tehát, mi történik: ~0000000000000000000000000000011111111111111111111111111111111000[/quote]Eredményül most 11111111111111111111111111111000-t kaptunk, ami a kettes számrendszerben a -8-nak felel meg. A következõ 2 sor tehát ugyanazt jelenti: Bin:~00000000000000000000000000000111=11111111111111111111111111111000Dec:~7=-8[/quote]Megint néhány példa: ~00000000000000000000000000000101 = 11111111111111111111111111111011~00000000000000000000000001110110= 11111111111111111111111110001010[/quote]Ehhez kapcsolódóan lenne itt egy fontos megjegyzendõ dolog. Aki nem érti, hogy miért -8 a megoldás, az nem olvasta el elég rendesen a \"Részletesebben a bitekrõl a Pawn-ban\" részt.1.4.5 Biteltolás (arithmetic shift, shiftelés) operátor(ok)A shift operátorok(<<, >>) a bitek tologatására használhatóak. A << balra tol, a >> jobbra. Bal oldalt található a szám, amit tologatni kívánunk, jobb oldalt pedig az, hogy mennyivel szeretnénk eltolni.1.4.5.1 Jobbra toló operátor(>>):Ez az operátor a szám bitjeit jobbra tolja a megadott mértékkel. Egy bittel való eltolás, 2-vel való osztásnak felel meg. Nézzünk egy példát: 00000000000000000000000000001000//8>>200000000000000000000000000000010//2[/quote]Láthatjuk, hogy semmi más nem történt, mint minden bitet 2-vel hátrébb mozgattunk. Az így \"megüresedett\" bitek helyére pedig 0-k kerültek. Lényegében az történt, hogy az utolsó 2 bit kiesett, az így megmaradt biteket 2-vel hátrébb csúsztattuk, majd az elsõ 2, így \"üres\" bit 0 értéket kapott.Nézzünk egy példát negatív számmal: 11111111111111111111111111110000//-8>>211111111111111111111111111111100//-2[/quote]Láthatjuk, hogy itt fontos szerepe van az MSB-nek. Ettõl függ ugyanis, hogy 0-k vagy 1-esek kerülnek a \"megüresedett\" bitek helyére. Fontos tehát megjegyezni, hogy a jobbra shiftelés megtartja a számok elõjelét, nem változtat azon!1.4.5.2 Balra toló operátor(<<):Ez az operátor a szám bitjeit balra tolja a megadott mértékkel. Egy bittel való eltolás, 2-vel való szorzásnak felel meg. Nézzünk egy példát: 00000000000000000000000000001000//8<<200000000000000000000000000100000//32[/quote]Itt megint könnyedén észrevehetõ mi is történt. Minden bitet fentebb \"toltunk\" 2-vel. Azok a bitek, amelyek ezáltal a 32. bit elé kerültek, kiesnek. Az így megüresedett bitek 0-kkal egészültek ki. Eddig nem túl bonyolult. Azonban figyeljünk oda, ha túl sokáig shiftelünk egy pozitív számot, akkor az utolsó érték, melyet fel tud venni a változónk (mielõtt még túlcsordulás miatt elérné a 0-t), az egy negatív szám!Nézzük mi történik negatív számok esetén: 11111111111111111111111111111000//-8<<211111111111111111111111111100000//-32[/quote]Láthatjuk hogy a balra shifteléskor a szám mindig 0-kkal egészítõdik ki, legyen az akár negatív, akár pozitív. Nem veszi figyelembe az MSB értékét, így ha túl sokat shiftelünk, akkor elõfordulhat, hogy a szám elveszti negatív értékét, és eléri a 0-t.1.4.6 Logikai biteltolás (logical shift, logikai shiftelés) operátorElõször is le kell tisztázni, hogy logikai shiftelésbõl csak egy létezik, jobbra logikai shiftelés(>>>). Sokban hasonlít a jobbra shiftelõ operátorra (>>), azonban van egy fontos különbség.Nézzük az elsõ példát: 00000000000000000000000000001000//8>>>200000000000000000000000000000010//2[/quote]Eddig nem igazán tapasztalhattunk semmi változást, minden pont ugyanúgy zajlott, mint a jobbra shiftelésnél(>>).Nézzük mi történik negatív szám esetén: 11111111111111111111111111111000//-8>>>200111111111111111111111111111110 //1073741822[/quote]Itt már láthatjuk a különbséget. Logikai shiftelés esetén az üres bitek helyére bekerülõ érték nem függ az MSB-tõl, hanem mindig 0. Emiatt soha nem kaphatunk eredményül negatív számot, logikai shiftelés esetén.1.5 Elsõ fejezet végeNos, ezennel átvettünk mindent, amit érdemes tudni a bitmûveletekrõl. Megmutattam az alapok, hogy hogyan épülnek fel a változók, valamint átvettük a fõ operátorokat. Eddig csak színtiszta elmélet volt. A következõ részben megmutatom, hogyan hasznosíthatjuk mindezt a gyakorlatban, vagyis mire is jó az a sok marhaság, amit eddig leírtam.2. Gyakorlati példák, érdekességek 2.1 Áttekintés, mikrõl is lesz még szóAz alábbiakban még pár dolgot szeretnék mutatni. Elõször is néhány \"függvénnyel\" fogok foglalkozni, majd mutatok még egy-két érdekességet. Konkrétan megmutatom, hogyan tudjuk egy jármû minden \"paneljének\" adatát kiíratni, részletesen(erre alapból nincs lehetõség, és a függvények is mind \"encode\" függvények, én viszont mutatok egy-egy decode \"verziót\" is.)Az alábbi 4 függvénnyel fogok foglalkozni: encode_tiresencode_panelsencode_doorsencode_lightsDe elõbb egy kis háttérinfó:A Pawnban a forrásban megadhatunk bináris számokat ugyanúgy, ahogyan Hex számokat is megadhatunk. Ehhez a \"0b\" prefixet kell használnunk. Tehát ha bináris számunk az 1011 és ezt akarjuk adni értékül egy változónak, azt így tehetjük meg: new var = 0b1011; 2.2 encode_tiresHáttér információ:A SA:MP-ban egy jármû abroncsainak adataid 4 bit tárolja. Az elsõ bit a jobb hátsó, a második bit a jobb elsõ, a harmadik bit a bal hátsó, a negyedik bit pedig a bal elsõ abroncsról tárol információt. Mivel 4 bitben eltárolhatóak az információk, így decimális formában 0-15 értékekkel is be lehet állítani a jármûvek abroncsait. 16-tól kezdve az értékek már ismétlõdni kezdenek, szóval a 16-nál az eredmény ugyanaz mint 0-nál, 17-nél ugyanaz mind 1-nél, és így tovább. Ez érthetõ, ha tudjuk hogyan épül fel a kettes számrendszer.(Ne felejtsük el, a biteket hátulról számozzuk!)Nos nézzük hogyan néz ki a kód: encode_tires(tire1, tire2, tire3, tire4){ return tire1 | tire2 << 1 | tire3 << 2 | tire4 << 3;} Láthatjuk, hogy bekérjük a 4 abroncs adatát(vagyis hogy ép[0], vagy kidurrant[1]), majd egy sima bitenkénti VAGY mûvelet és biteltolás kombinálásával visszatérünk az eredménnyel. A kódban a tire1 felel meg a jobb hátsó abroncsnak, a tire2 a jobb elsõ, a tire3 a bal hátsó, és a tire4 a bal elsõnek. Nézzünk egy konkrét példát. Tegyük fel, hogy én a hátsó 2 abroncsot ki akarom durrantani, még az elsõ kettõt épre akarom beállítani. Ez esetben így néz ki a dolog: encode_tire(1,0,1,0); Ha kiíratnánk konzolba a visszatérési értéket (%d-vel), 5-öt kapnánk. Miért is? Az 5 bináris alakja 0101. Hoppá, ha megnézzük ez pont fordítottja annak, amit mi megadtunk számjegyekként, csak már konkrét bináris számként. Nézzük hogyan is történhetett mindez, mit is csináltunk. A függvény a következõ mûveletet végzi: tire1 | tire2 << 1 | tire3 << 2 | tire4 << 3 Konyhanyelven úgy magyarázhatnánk el a dolgot, hogy ez a kód egymás mellé pakolgatja a megadott számokat, fordított sorrendben. Na de mi most nem a konyhanyelv miatt vagyunk ebben a leírásban, szóval nézzük a kódunkat. Lényegében ez futott le, a konkrét értékeinkkel nézve: 1 | 0 << 1 | 1 << 2 | 0 << 3 Bármily furcsa is, ez egy mûvelet, melyen szépen végig halad sorban a program. Elõször is írjuk át az egészet bináris alakba(az egyszerûség kedvéért most csak 4 bitet fogok kiírni): 0001 | 0000 << 1 | 0001 << 2 | 0000 << 3 Na erre se mondtuk volna rá a tutorial elsõ részének elolvasása elõtt, hogy ez egy mûveletsor. A rendszer elsõnek az eltolásokat végzi el (olyan ez mint a 2*3+2*4+2*5 mûveletben elõször a szorzásokat végezzük el, és csak utána adunk össze, ha nincs zárójel), és csak utána a bitenkénti VAGY mûveleteket, ezért mi is így fogunk tenni. Emlékezzünk, eltoláskor az \"üres\" bitek 0 értéket fognak felvenni. Végezzük el külön csak az eltolásokat: 0000 << 1 = 00000001 << 2 = 01000000 << 3 0000 Nos, ha ezeket elvégeztük, akkor nézzük, hogy is áll most a kódunk: 0001 | 0000 | 0100 | 0000 Négy azonos mûvelet van hátra, így hát a program innen más szépen sorban fog haladni. Tegyünk mi is így. Elsõ tehát a 0001 | 0000 mûvelet elvégzése. Emlékeztetõül, a bitenkénti VAGY mûvelet akkor ad 0-t eredményül, ha mind a kettõ érték 0, minden más esetben 1-et. Végezzük el külön az elsõ bitenkénti VAGY mûveletet: 0001 | 0000 = 0001 Nem volt bonyolult, nézzük tehát hogy áll akkor most a teljes mûveletünk: 0001 | 0100 | 0000 Haladunk szépen tovább, elvégezzük a 0001 | 0100 mûveletet. 0001 | 0100 = 0101 Nincs mit magyarázni rajta, egyszerû bitenkénti VAGY mûvelet volt ez is. Már csak ez van hátra: 0101 | 0000 Nos, ez sem lesz egy bonyolult mûvelet: 0101 | 0000 = 0101 Ezzel meg is kaptuk a végeredményt, ami nem más, mint a 0101 (tízes számrendszerben 5). Mivel azonban eléggé kusza lett így a számításunk, nézzük egyben hogyan is néz ki az egész, ha nem bontom ennyire részekre: 0100 | 0000 << 1 | 0001 << 2 | 0000 << 3v0001 | 0000 | 0100 | 0000v0001 | 0100 | 0000v0101 | 0000v0101 Nos, ezennel láthattuk a gyakorlatban is, hogyan mûködik a függvény. Kijelenthetjük tehát, hogy a függvény a számokat a sorrendjüknek megfelelõ számú bitbe helyezi. Mivel azonban a biteket hátulról számozzuk, így az 1,0,1,0 bemenetek végeredménye 0101 lesz. Így már nem is olyan bonyolult Na, ha már ilyen sokáig eljutottunk, akkor felhasználhatjuk ezt a kódot arra, hogy az alapjait figyelembe véve elkészítsük a decode változatot. Mivel 4 értékkel térünk vissza, ezért paraméterként kell majd 4 változó, melyeknek módosíthatjuk az értékét, illetve 1 paraméter pedig a dekódolandó szám. A függvényünk \"címsora\" tehát így néz ki: decode_tires(tires, &tire1, &tire2, &tire3, &tire4) Hogy példánk ne csak elmélet legyen, a tires változó legyen az elõbbi 0101 értékkel egyenlõ. Na akkor menjünk tovább. Bemeneti értékünk ugye a \"tires\" változóban lesz eltárolva, így ezzel fogjuk a mûveleteket végezni. Kezdjünk el gondolkozni. Hogyan is tudnánk ellenõrizni egy adatot. Legegyszerûbb ha a bitenkénti ÉS mûveletet alkalmazzuk. Miért is? Nos, a bitenkénti ÉS mûvelet ugye akkor ad eredményül 1-et, ha mind a 2 érték egy. Tehát ha min egy számot összehasonlítunk a 1-el(binárisan ugye 00000000000000000000000000000001, akkor az eredményünk lényegében arról fog tájékoztatni, hogy az elsõ bit 0, vagy 1. Nem is olyan bonyolult ez. Nézzük akkor az elsõ bitet. Arra emlékezhetünk még, hogy az elsõ bit a tire1, vagyis jobb hátsó abroncs adatait tárolja. Tehát: tire1 = tires & 1; Vagy konkrétan a példánk esetében: tire1 = 0101 & 0001 Mivel a bitenkénti ÉS mûvelet akkor ad 1-et eredményül, ha mind a kettõ bit 1, akkor már láthatjuk is, hogy miért elegendõ az elsõ bitet szemügyre venni. Mivel mind a 2 helyen az elsõ bit 1, ezért a tire1 változónk értéke is 1(0001) lesz. Egyszerû és nagyszerû, máris megkaptuk a jobb hátsó abroncs állapotát.Gondolkozzunk hát tovább. Nekünk most arra van szükségünk, hogy a 2. bit értékét megkapjuk. Hogyan kaphatjuk ezt meg? Legegyszerûbb megoldás, ha a tires változót jobbra shifteljük 1-el, majd ismételten bitenkénti ÉS mûveletet végzünk. Nézzük tehát, hogyan is nézne ez ki: tire2= tires >> 1 & 1; Vagy konkrétan a mi példánkban: tire2 = 0101 >> 1 & 0001vtire2 = 0010 & 0001 Ismételten bitenkénti ÉS mûvelet, és az eredményünk 0, mivel csak az egyik bit 1, a másik 0, így a bitenkénti ÉS mûvelet eredménye is 0. Ezen az elven megcsináljuk a maradék 2 abronccsal is: tire3= tires >> 2 & 1;tire4= tires >> 3 & 1; Vagy a mi esetünkben konkrét példánkkal: tire3 = 0101 >> 2 & 0001 // azaz: --> 0001 & 0001 = 1tire4 = 0101 >> 3 & 0001 // azaz: --> 0000 & 0001 = 0 A teljes kódunk tehát: decode_tires(tires, &tire1, &tire2, &tire3, &tire4){ tire1 = tires & 1; tire2 = tires >> 1 & 1; tire3 = tires >> 2 & 1; tire4 = tires >> 3 & 1;} A konkrét példánkban a 4 változónk értéke a függvény lefutása után: tire1: 1tire2: 0tire3: 1tire4: 0 Ha összehasonlítjuk, ezek pont azok az értékek, mint amiket az encode függvényben megadtunk, tehát úgy tûnik jól dolgoztunk 2.3 encode_lightsEz a rész lesz a legrövidebb. Az egész lámpás rész pontosan ugyanarra a sémára épül mint az abroncsok. Az adott bitek ugyanazon a \"helyen\" lévõ lámpa adatait szolgáltatják, ugyanúgy 0 az ép, és 1 a sérült lámpák állapot azonosítója, és ugyanúgy 1-1 bit tárolja az értékeket.Maga a kód: encode_lights(light1, light2, light3, light4){ return light1 | light2 << 1 | light3 << 2 | light4 << 3;} Láthatjuk hogy halál pontosan ugyanaz mint az encode_tires, csak más változónevekkel. Így hát a decode függvény is ugyan az lesz, csak a változóneveket kell módosítanunk: decode_lights(lights, &light1, &light2, &light3, &light4){ light1 = lights & 1; light2 = lights >> 1 & 1; light3 = lights >> 2 & 1; light4 = lights >> 3 & 1;} Ha megnézzük látjuk, hogy teljesen ugyan az a séma, ugyan az a felépítés, minden megegyezik, ezért nem is írtam le újból ugyanazt, amit egyszer már kifejtettem.2.4 encode_panelsHáttér információ:A SA:MP 7 panelrõl tárol adatokat. Egy panel adatait 4 bit írja le. Nos, ennél több infóra nekünk most nincs szükségünk, az elõzõ kód alapján ezen kód is megérthetõ, így az encode verzióhoz nem is nagyon szeretnék magyarázatot fûzni. A különbség az encode_tires-hez képest, hogy itt 4-essével shiftelünk, mivel 1 panel adatait 4 bit tárolja.Maga a kód: encode_panels(flp, frp, rlp, rrp, windshield, front_bumper, rear_bumper){ return flp | frp << 4 | rlp << 8 | rrp << 12 | windshield << 16 | front_bumper << 20 | rear_bumper << 24;} Nézzünk egy decode verziót, ehhez a kódhoz. Alapjaiban ugyanazon a vonalon kell elindulnunk, mint a decode_tires esetében. Itt most 7 visszatérési értékünk lesz, és 1 bemeneti érték. Tehát így néz ki a függvény header: decode_panels(panels, &flp, &frp, &rlp, &rrp, &windshield, &front_bumper, &rear_bumper) A folytatásban megint csak az elõbbi verziót kell alapul vennünk, azonban itt lesz egy kis különbség. Egyértelmû, hogy mivel 1 panel adatait 4 bit tárolja, így a 4 bitet nekünk egyben kell lekérni, és egyben kell vele visszatérnünk. Megint csak a bitenkénti ÉS mûveletet fogjuk alkalmazni, azonban mivel 4 bitrõl van szó, így egy olyan számmal kell összehasonlítanunk, mely bináris alakban 1111. Ez a szám nem más, mint a 15. Hogy miért ez kell?Ha egy bitsorozat tartalmát ki szeretnénk másolni egy másik változóba, akkor a bitenkénti ÉS mûveletre vna szükségünk. A bitenkénti ÉS mûvelet akkor ad 1-et eredményül, ha mindkét bit 1, ha valamelyik 0, akkor a visszatérés már 0 lesz. Ergó tehát, ha egy csupa 1-esekbõl álló bitsorozattal vetjük össze az eredeti bitsorozatunkat, akkor lényegében lemásoljuk a sorozatot, egy másik változóba, hisz eredményül mindig az eredeti sozatot fogjuk visszakapni. Íme néhány példa: 0101 & 1111 = 01011111 & 1111 = 11110000 & 1111 = 00001011 & 1111 = 1011 Láthatjuk tehát, hogy ha csupa 1-est tartalmazó bitsorozattal hasonlítjuk össze az eredeti számunkat, akkor lényegében egy másolatot készítünk. Mivel nekünk erre a 4 bitre van szükségünk a 32-bõl, így pontosan ezt kell tennünk, ezen 4 bitet lemásolni egy másik változóba, hogy késõbb ezt majd egy másik függvénnyel még jobban darabokra szedhessük.Íme tehát akkor a kódunk elsõ sora: flp = panels & 15; Mivel 4-esével vizsgáljuk a biteket, így lényegében annyit kell még változtatnunk az elõzõ kódon(decode_tires), hogy az 1-esével növekvõ shiftelések helyett 4-esével shifteljünk. Azaz a 2. panel adatainak lekérdezése: frp = panels >> 4 & 15; Innen pedig már csak mindig további 4 shiftelést el kell végeznünk, és szépen végig haladhatunk a panels változó bitjein, és megkaphatjuk minden panel adatait külön-külön. rlp = panels >> 8 & 15;rrp = panels >> 12 & 15;windshield = panels >> 16 & 15;front_bumper= panels >> 20 & 15;rear_bumper = panels >> 24 & 15; Ezennel minden panel adatait megkaptuk. Ezután már akár a decode_tires függvényt felhasználva szét lehet szedni az adatokat darabokra, de akár egy saját névvel, és más változó nevekkel ellátott másik függvényt is létre lehet hozni a decode_tires mintájára.Tehát az egész kód egyben: decode_panels(panels, &flp, &frp, &rlp, &rrp, &windshield, &front_bumper, &rear_bumper){ flp = panels & 15; frp = panels >> 4 & 15; rlp = panels >> 8 & 15; rrp = panels >> 12 & 15; windshield = panels >> 16 & 15; front_bumper= panels >> 20 & 15; rear_bumper = panels >> 24 & 15;} Igazából a függvény ugyanarra a sémára épül mint a decode_tires, csak annyi a különbség, hogy 4 bitenként kapjuk az adatokat, nem pedig egyesével, mivel itt minden panelnek 4 adatát tudjuk majd még lekérdezni. Nem tudom lehet-e még ehhez a részhez hozzáfûzni valamit, szóval szerintem ugorjunk. 2.5 encode_doorsHáttér információ:A SA:MP képes minden jármû 4 \"ajtajának\" állapotát tárolni. Ezek az \"ajtók\"(Doors-t hogy fordíthatnám jobban?) konkrétan a motorháztetõ, csomagtartó, vezetõoldali ajtó, és anyósülés oldali ajtó. A 2 hátsó ajtó állapotát jelenleg még nincs lehetõségünk lekérdezni.Amikor lekérdezzük egy jármû \"ajtajának\" adatait, azt a SA:MP 4 bájtként(32 bit) adja vissza. Ebbõl a 4 bájtból 1 bájt(8 bit) jut minden egyes objektumra. Vagyis minden egyes objektum adatait 8 bit tárolja. Vagyis tárolhatná...A SA:MP csak az elsõ 3 bitet használja, a további bitek üresek lesznek, nincs funkciójuk, talán majd késõbb lesz. Az elsõ bit azt tárolja, hogy az objektum zárt[0], vagy nyitott[1](nem az jelenti, hogy bezárt, hanem becsukott/kinyitott) állapotban van-e. A második bit azt adja meg, hogy az objektum ép[0] vagy sérült[1]. A 3. bit arról tájékoztat, hogy az adott objektum rajta van-e még a jármûvön[0] vagy már leesett[1]. A többi bit mint mondtam üres. Ez majd a decode függvénynél lesz fontos.Nézzük tehát a konkrét kódot: encode_doors(bonnet, boot, driver_door, passenger_door, behind_driver_door, behind_passenger_door){ #pragma unused behind_driver_door #pragma unused behind_passenger_door return bonnet | boot << 8 | driver_door << 16 | passenger_door << 24;} A 2 hátsó ajtóval nem foglalkozunk, mivel azok jelenleg még nem támogatottak ugyebár, de a kód készítõje gondolt a jövõre is, vagy fogalmam sincs miért adta meg ezt a két paramétert is, hisz ígyis-úgyis módosítani kell a kódon... Ha behoznak még 2 ajtót, akkor az 1 bájtos tárolással már 6 bájt lenne, de 1 változó maximum 4 bájt lehet, tehát a kódot úgyis módosítani kell, ha meg arra gondoltak, hogy legalább a paramétereket nem kell majd szegény scriptereknek minden sorban átírni, az megint hülyeség, hisz ki adna értéket olyan paraméternek, amely nincs használva?...Nos mindegy, nem a kritizálásért vagyunk most itt. Ha megnézzük, a kód 8-as shiftelést használ, mivel egy objektum adatait 8 bit tárolja. A mûködési elv ugyanaz, mint az elõzõ encode függvényeknél, így erre nem kívánok megint kitérni.Nézzük azonban hogyan írnánk ehhez egy decode függvényt. Mikor megkérdeztem errõl ismerõsömet, õ azt mondta, ugyanúgy mint az elõzõ, decode_panels függvénynél, csak 8-as shiftelést használunk, és 15 helyett 255-el végezzük a bitenkénti ÉS mûveletet. 255-el? minek?No erre is ki fogok térni késõbb, de haladjunk a szokásos módon. 1 bemeneti paraméterünk van, és 4 kimeneti, vagyis a header rész a következõ: decode_doors(doors, &bonnet, &boot, &driver_door, &passenger_door) Ezzel meg is vagyunk, itt nem látok különösebb magyarázni valót. Én most nem fogok törõdni a 2 nem létezõ ajtóval, ha egy jövõbeni verzióban netán azokat is behoznák mint lekérdezhetõ objektum, akkor ígyis-úgyis módosítani kell majd a kódot, addig meg úgysincs rá szükségünk.Nos, mint az eddigi decode függvényeknél, elsõ feladatunk egy shiftelés mentes bitenkénti ÉS mûvelet elvégzése. Az egyik megoldás a következõ: bonnet = doors & 255; Lehet így is. Azonban véleményem szerint felesleges itt egy ekkora számmal \"szenvednünk\". Miért használjunk ekkora számot, és ezáltal bonyolítsuk feleslegesen a kódunkat, ha nekünk a 8 bitbõl csak az elsõ 3 lesz ami adatot tárol? Bõven elegendõ egy olyan szám, amely bináris alakja 111. Ez nem más, mint a 7. Tehát a következõ megoldás ugyanúgy helyes: bonnet = doors & 7; A két kód pontosan ugyanazt az eredményt fogja visszaadni, semmi különbség a kettõ között. Csupán elsõ esetében nagy számmal dolgozunk, még második verzió esetén csak akkorával, amekkora szükséges.A kód többi részében csak 8-as shifteléssel végigcammogunk a doors változó többi bitjén is: boot = doors >> 8 & 7;driver_door = doors >> 16 & 7;passenger_door = doors >> 24 & 7; A teljes kód egyben: decode_doors(doors, &bonnet, &boot, &driver_door, &passenger_door){ bonnet = doors & 7; boot = doors >> 8 & 7; driver_door = doors >> 16 & 7; passenger_door = doors >> 24 & 7;} A kód pont ugyanazon az elven épül fel, mint az összes többi decode függvényünk, csak épp a benne használatos értékek az \"ajtó\" objectumok adatstruktúrájához vannak igazítva.2.6 Egyéb ötletek gyakorlati alkalmazásra, hasznosításraJoggal felmerülhet a kérdés, hogy ennyi? Emiatt rágtuk végig magunkat ezen néhány tízezer karakteren? Nem. Nem ennyi. A SA:MP-ban rengeteg felhasználási módja lehet még a bitmûveleteknek. Mutatok is néhány dolgot, mire jók még.2.6.1 RGBA->HEX; HEX->RGBA konvertálásokNos, bármily \"hihetetlen\", erre is használható. Miért is? Nos, 16-os számrendszerben 0-9, valamint A-F \"szánjegyek\" léteznek. A hexadecimális számjegy decimális értékben 0-15 lehet. Ha megnézzük bináris értékeket, 0-15 az 4 bittel leírható, hiszen a 15 az bináris alakban 1111. De ha józan paraszti ésszel belegondolunk is. 16 az 24. Így tehát 1 hexadecimális érték, az 4 bináris értékkel írható le. Ebbõl már sejthetõ, hogy itt is shiftelésre lesz szükségünk. Naná Mivel 4 bit ír le egy hexadecimális számjegyet, így 4-esével kell majd shiftelnünk.Mi is az az RGBA?RGBA: A színek olyan meghatározási módja, melyben a 3 alapszín([r]ed, [g]reen, lue) mellett egy 4. érték, az úgynevezett [a]lpha is szerepel, amely az áttetszõséget adja meg. A legtöbb program(pl. paint, PS), és pl. a css(külön függvénnyel!) is elfogadja ezen értékeket decimális számokként(0-255) is, a SA:MP-ban azonban hexadecimális alakban kell megadnunk. Bármily furcsa a következõ 4 sor pont ugyan azt jelenti:\"Normál\" RGB alak(pl. paint, photoshop): Piros: 0, Zöld: 255, Kék: 255CSS alak(függvénnyel): rgba(0, 255, 255, 1);Pawn alak(1): 0x00FFFFFFPawn alak(2): 16777215 Ó igen, a Pawn-ban decimális számokként is megadhatjuk a színeket, hisz a program mindent bináris számrendszerbe fog átszámolni. De akár bináris alakban is megadhattuk volna:Pawn alak(3): 0b111111111111111111111111 Az, hogy mi a Pawn-nak hexadecimális alakban szoktuk megadni, annak az egyetlen oka, hogy így a leginkább \"átlátható\", és ez egy amúgy is elfogadott módja (mármint a hexadecimális számmal való megadás) a színek leírásának a számítástechnikában.Nos, mivel 1 szín 0 és 255 közötti értéket tartalmaz a piros, zöld és kék színekbõl, ez hexadecimálisan 2 számjeggyel leírható: 00-FF. Az FF értéke 255, aki nem hiszi, számoljon utána (162-1)2 hexadecimális számjegy az pedig 8 bináris számjeggyel leírható(00000000-11111111). Így már láthatjuk, hogy 8-as shiftelésre lesz szükségünk. Másra nincs is szükségünk, az RGBA->HEX konverter elkészítéséhez.stock RGBAToHex( r, g, b, a ){return a | b << 8 | g << 16 | r << 24;} Az, hogy miért fordított sorrendben kellett megadnunk az értékeket, az megint csak azzal magyarázható, hogy a biteket hétköznapi értelemben véve visszafelé számozzuk.Egy ellentét, azaz HexToRGBA függvény megírása sem túl bonyolult, ugyan ezt kell csinálnunk, csak visszafelé.stock HexToRGBA( colour, &r, &g, &b, &a ){a = colour & 0xFF;b = (colour >> 8 ) & 0xFF;g = (colour >> 16 ) & 0xFF;r = (colour >> 24 ) & 0xFF;} A 0xFF-et természetesen decimális értékkel is megadhatjuk(255).No akkor lehet ugye RGBA-t ARGB-be is konvertálni, az így néz ki:RGBAToARGBHEX(r,g,b,a){return (r & 0xff) << 24 | (g & 0xff) << 16 | (b & 0xff) << 8 | a & 0xff;} Ez máris egy újabb felhasználási módja a bitmûveleteknek. De, mutatok még 2.6.2 Boolen változók helyett IntegerAz al-al-alcímet elolvasva joggal felmerülhet a kérdés, hogy ez meg hogy jön ide. Nos ez úgy jön ide, hogy most egy kis memóriatakarékosságról fogok itt regélni. Adhattam volna azt is címnek, hogy miért ne használjunk boolean változókat, ha legalább 5 igaz/hamis értéket el akarunk tárolni.Nos akkor egy kis memórialecke. Azt már tisztáztuk, hogy az integer változók 32 bitesek, vagyis 4 bájtot foglalnak le a memóriában maguknak. A boolean típusú változókról azonban még nem volt szó. Mivel 1 értéket tárolnak csak, az is csak igaz vagy hamis lehet, így joggal gondolhatnánk, hogy 1 bit a mérete. Azonban nem...1 boolean változó 8 bitet, azaz 1 bájtot foglal le magának a memóriában. Hogy ez miért így van, az a számítógépek memóriahasználatával magyarázható. Manapság nincs mód arra, hogy a memóriában egyetlen bitet módosítsunk, csak 8-as \"csomagokban\", vagyis bájtonként tudjuk ezt megtenni. Hogy ez miért van így, azt most nem fogom itt fejtegetni Nos, ha megnézzük tehát, a boolean változó egy nagyon-nagyon pazarló megoldás. Hiszen 1 darab boolean változó 1 bájtot foglal, 4 darab boolean változó esetén ez már 4 bájt, ami megegyezik 1 integer változó méretével. Azonban, még a 4 boolean változóban 4 igaz/hamis érték kerülhet tárolásra, addig egy integerben 32 igaz/hamis értéket is eltárolhatunk.Bizony, 32 igaz/hamis érték. Hiszen 1 integer változó 32 bitbõl áll, ha minden bitet hasznosítunk, akkor a bitmûveletek felhasználásával 32 igaz/hamis értéket is eltárolhatunk. Összehasonlítva:32 darab boolean változó mérete 32 bájt.1 integer változó mérete: 4 bájt. Vagyis 32 boolean változó esetén nyolcad annyi memóriánkba kerül, ha egy darab integerben tároljuk el az értékeket. Persze 32 booleanra ritkán van szükség, de egy RP modban simán elõfordulhat. Gondoljunk csak bele... Van 32 boolean-tömbös változónk, ez a maximum slotszámot(1000) figyelembe véve: 32*1000 byte azaz 32000 byte ami ~31 kilobyte. Ha ezt egyetlen integer-tömbbel oldanánk meg: 4*1000 byte, azaz 4000 byte, ami ~3,9 kilobyte. Azért így nézve már nem annyira \"elhanyagolandó\" az a különbség szerintem =)Akár írhatnánk erre külön egy függvényt, mely az adott bit értékével tér vissza. Nem is bonyolult. Így már azok számára is használhatóak a bitmûveletek, akik nem értenek hozzá, és rengeteg memóriát takaríthatnak meg.Íme egy függvény az adatok kiolvasására:stock GetData(integer, bit){ return integer >> bit & 1; //vagy az 1 helyett akár 0x01 alakban is megadhatjuk, nincs jelentõsége} És a párja, adat írásra:stock SetData(&integer, bit, value){ integer |= value << bit; return 1;} Persze ezt még lehetne csinosítani, hogy pl. ne 0-tól hanem 1-tõl számoljuk a biteket, vagy hogy 31(vagy 32 ha 1-tõl számolunk) felett 0 legyen a return értéke, esetleg a value-re is rakhatnánk ellenõrzést, hogy 0-1-e az értéke, vagy más, de ilyenekkel most nem törõdtem.2.6.3 Több érték, egy integerbeNa ez az a rész, ami véleményem szerint már csak a nagyon megrögzött memóriafelszabadítók szerint nem lesz felesleges Amennyiben nincs szükségünk túl nagy számokra(pl. csak 0-15, vagy akár csak 0-7, de akár még a 0-255 is megfelel), akkor akár egy integerbe is tehetjük õket. Hogyan? Egyszerû. Csoportosítjuk a biteket. Tegyük fel, hogy maradván egy RP szervernél, el akarjuk tárolni ûbergigantikusan memóriakímélõ megoldással a játékos házának számát(ez legyen mondjuk 0-100 közötti szám), a játékos frakcióját(ez legyen 0-15 között), a játékos rangját(0-7 között), hogy a telefonja be van-e kapcsolva(0-1), a játékos szolgálati jármûvének id-jét(legyen 0-255 között), és az alapmunkáját(0-15 között). Ez is megoldható, egyetlen integerben, és ismételten 1000 slottal számolva máris megspóroltunk 4*4*1000+1*1000 byte-t(5 integer, és 1 boolean helyett csupán 1 integert használunk), ami ~16,6 kilobyte. Azért nem mindegy az, hogy 20,5 kb-ot vagy 4 kb-ot használunk szerintem... Ezt a következõ módszerrel valósíthatnánk meg:#define DATA_HOUSE 0#define DATA_FACTION 1#define DATA_RANK 2#define DATA_PHONESTATE 3#define DATA_DUTYCAR 4#define DATA_JOB 5stock GetData(playerinfo, data){ switch(data) { case DATA_HOUSE: return playerinfo & 127;//7 bit case DATA_FACTION: return playerinfo >> 7 & 15;//4 bit case DATA_RANK: return playerinfo >> 11 & 7;//3 bit case DATA_PHONESTATE: return playerinfo >> 14 & 1;//1 bit case DATA_DUTYCAR: return playerinfo >> 15 & 255;//8 bit case DATA_JOB: return playerinfo >> 23 & 15;//4 bit}return -1;}stock SetData(&playerinfo, data, value){ switch(data) { case DATA_HOUSE: { playerinfo = playerinfo & ~127 | value; }//7 bit (0-127 közötti maximálisan) case DATA_FACTION: { playerinfo = playerinfo & ~(15 << 7) | value << 7; }//4 bit (0-15 között) case DATA_RANK: { playerinfo = playerinfo & ~(7 << 11) | value << 11; }//3 bit (0-7 között) case DATA_PHONESTATE: { playerinfo = playerinfo & ~(1 << 14) | value << 14; }//1 bit (0-1) case DATA_DUTYCAR: { playerinfo = playerinfo & ~(255 << 15) | value << 15; }//8 bit (0-255 között) case DATA_JOB: { playerinfo = playerinfo & ~(15 << 23) | value << 23; }//4 bit (0-15 között) } return -1;}stock SetDatas(&playerinfo, house, faction, rank, phonestate, dutycar, job){playerinfo |= house | (faction << 7) | (rank << 11) | (phonestate << 14) | (dutycar << 15) | (job << 23);} Ez már azért elég morbid, ezt inkább csak érdekességképp mutattam, de éppenséggel ez is használható lenne, csak ehhez már kell egy kis õrültség 2.7 Második fejezet végeNos, láthattuk hogyan is mûködik mindazon sok \"marhaság\", amit az elsõ fejezetben elméletként átvettünk. Láthattuk hogyan mûködnek a jármûvek paneljének adataival babráló encode függvények, valamint megmutattam, hogyan írhatunk hozzájuk decode verziókat. Láthattunk még példát egyéb gyakorlati hasznosításra, kicsit megkoccoltuk a hexadecimális számok világát is, majd szóba került egy kis memóriatakarékosság. Végül mutattam egy összetettebb, elvetemült példát is, a bitmûveletek hasznosítására.3. Összegzés, végszó Úgy gondolom, hogy elég részletesen körbe jártuk a témát. Megnéztük a bitmûveletek elméleti, és gyakorlati oldalát is, láthattunk gyakorlati példákat. Nem kell elsõre megérteni ezt az egészet, sõt... Elegendõ ha egyszer elolvassuk, emésztjük kicsit, majd késõbb megint elõvesszük, akkor netán már kicsit többet is megértünk belõle. Nem egy kihagyhatatlan része a Pawno-nak, de azért van annak haszna, ha az ember jártas ebben a témában is, bár sokak szerint nem éri meg a sok fáradtságot, és idõt, mely a megértéséhez, begyakorlásához, használatának elsajátításához szükséges. Próbáltam mindent részletesen kifejteni. A bonyolultabb dolgokhoz sokat magyarázni, az egyszerûbbek esetén csak visszautalni az alapokhoz. Sok dolgot talán már túl is magyaráztam. Ezt a leírást nem 1 nap alatt készítettem el, kb. 1 hónapja írtam meg az elméleti oldalát, most az elmúlt pár napban pedig a gyakorlatot. Esténként 1-2 órát szántam rá csupán, és mivel ilyenkor már nem vagyok épp a toppon, elképzelhetõ, hogy néhány helyen helyesírási, vagy elvi hibát vétettem. Amennyiben valaki ilyet találna, kérem szóljon, és korrigálni fogom. Nos, azt hiszem nincs is más hátra, mint hogy sok szerencsét kívánjak a bitmûveletek elsajátításához, és megosszam a forrásokat. Remélem néhányan hasznosnak találják, eme igen hosszúra sikerült leírást.4. Források: Internet:WikipédiaSA:MP WikiSA:MP hivatalos fórumKönyv, e-book:Pohl László - A programozás alapjai(BME - Elektronikus Eszközök Tanszéke, Budapest, 2010)Siroki László - C programozás - Bitmûveletek jegyzeteˆ Anthony, 2012 - Minden jog fenntartva.A leírás egészben, vagy részleteiben szabadon terjeszthetõ, ameddig egyértelmûen fel van tüntetve az eredeti készítõ, valamint hogy honnan is származik. « Utoljára szerkesztve: 2012. augusztus 01. - 10:53:54 írta Anthony » Naplózva
00000000000000000000000000000011 | 00000000000000000000000000000101 = 0000000000000000000000000000011100000000000000000000000001110110 | 00000000000000000000000001011001 = 00000000000000000000000001111111[/quote]1.4.3 Bitenkénti (bitek közötti) KIZÁRÓ VAGY mûveletA bitenkénti KIZÁRÓ VAGY mûvelet (XOR, ^) egy logikai operátor. Ez az operátor nagyon hasonlít a VAGY operátorra, az a fõ különbség a kettõ között, hogy amennyiben mindkét bit 1-es, akkor a KIZÁRÓ VAGY operátor 0-t ad eredményül, a többi esetben ugyanúgy viselkedik mint a VAGY operátor. Igazság táblázata: A B A^B000101011110[/quote]Példának nézzük meg, mi történik, ha a bitenkénti KIZÁRÓ VAGY operátort alkalmazzuk, szintén a 11 és a 7 esetén. A 11 bináris számrendszerben mint már írtam 00000000000000000000000000001011-nek felel meg, a 7 pedig 00000000000000000000000000000111-nek. Tehát a 11 ^ 7, az ugyaz mint ha ezt írnám: 00000000000000000000000000001011 ^ 00000000000000000000000000000111. Az átláthatóság kedvéért ismét egymás alá fogom írni a 2 számot, hogy áttekinthetõbb legyen, és az eredményt pedig a 3. sorba: 00000000000000000000000000001011^0000000000000000000000000000011100000000000000000000000000001100[/quote]Eredményül most 00000000000000000000000000001100-t kaptunk, ami a kettes számrendszerben a 12-nek felel meg. Érdekes, hogy teljesen más eredményt kapunk, pedig lényegében ugyanazt az eredményt adja az operátor, - kis különbséggel ugyebár - mint a VAGY operátor.A következõ 2 sor tehát ugyanazt jelenti: Bin:00000000000000000000000000001011^00000000000000000000000000000111=00000000000000000000000000001100Dec:11^7=12[/quote]Megint néhány példa: 00000000000000000000000000000011 ^ 00000000000000000000000000000101 = 0000000000000000000000000000011000000000000000000000000001110110 ^ 00000000000000000000000001011001 = 00000000000000000000000000101111[/quote]Ezzel kapcsolatban egy érdekességet mutatnék meg. Vegyünk 2 számot. A látványosság kedvéért én példának a 118-at és a 89-et veszem. 1. szám: 00000000000000000000000001110110 = 1182. szám: 00000000000000000000000001011001 = 89Mûvelet elvégzése: 00000000000000000000000001110110 ^ 00000000000000000000000001011001 = 00000000000000000000000000101111 = 471. szám az eredmény, 2. szám az eredeti 2. szám: 00000000000000000000000001011001 = 89Eredmény: 00000000000000000000000000101111 ^ 00000000000000000000000001011001 = 00000000000000000000000001110110 = 118[/quote]Vagyis visszakaptuk a kiindulási állapotot 1.4.4 Bitenkénti (bitek közötti) NEGÁLÁSA bitenkénti NEGÁLÁS (NOT, ~) az utolsó logikai operátor, melyet meg fogok mutatni. Ez az operátor eléggé eltér az eddigiektõl, leginkább abban, hogy ezt a mûveletet nem 2 bitre alkalmazzuk, hanem csak 1-re. Szokás komplementer-operátornak is hívni(konyhanyelven). Lényegében az 1-esekbõl 0-t, a 0-kból 1-est csinál. Igazság táblázata: A ~A0110[/quote]Példának nézzük meg, mi történik, ha a bitenkénti NEGÁLÁST alkalmazzuk a 7-es számra. A 7 bináris számrendszerben mint már írtam 00000000000000000000000000000111-nek felel meg. Tehát a ~7, az ugyaz mint ha ezt írnám: ~00000000000000000000000000000111. Nézzük tehát, mi történik: ~0000000000000000000000000000011111111111111111111111111111111000[/quote]Eredményül most 11111111111111111111111111111000-t kaptunk, ami a kettes számrendszerben a -8-nak felel meg. A következõ 2 sor tehát ugyanazt jelenti: Bin:~00000000000000000000000000000111=11111111111111111111111111111000Dec:~7=-8[/quote]Megint néhány példa: ~00000000000000000000000000000101 = 11111111111111111111111111111011~00000000000000000000000001110110= 11111111111111111111111110001010[/quote]Ehhez kapcsolódóan lenne itt egy fontos megjegyzendõ dolog. Aki nem érti, hogy miért -8 a megoldás, az nem olvasta el elég rendesen a \"Részletesebben a bitekrõl a Pawn-ban\" részt.1.4.5 Biteltolás (arithmetic shift, shiftelés) operátor(ok)A shift operátorok(<<, >>) a bitek tologatására használhatóak. A << balra tol, a >> jobbra. Bal oldalt található a szám, amit tologatni kívánunk, jobb oldalt pedig az, hogy mennyivel szeretnénk eltolni.1.4.5.1 Jobbra toló operátor(>>):Ez az operátor a szám bitjeit jobbra tolja a megadott mértékkel. Egy bittel való eltolás, 2-vel való osztásnak felel meg. Nézzünk egy példát: 00000000000000000000000000001000//8>>200000000000000000000000000000010//2[/quote]Láthatjuk, hogy semmi más nem történt, mint minden bitet 2-vel hátrébb mozgattunk. Az így \"megüresedett\" bitek helyére pedig 0-k kerültek. Lényegében az történt, hogy az utolsó 2 bit kiesett, az így megmaradt biteket 2-vel hátrébb csúsztattuk, majd az elsõ 2, így \"üres\" bit 0 értéket kapott.Nézzünk egy példát negatív számmal: 11111111111111111111111111110000//-8>>211111111111111111111111111111100//-2[/quote]Láthatjuk, hogy itt fontos szerepe van az MSB-nek. Ettõl függ ugyanis, hogy 0-k vagy 1-esek kerülnek a \"megüresedett\" bitek helyére. Fontos tehát megjegyezni, hogy a jobbra shiftelés megtartja a számok elõjelét, nem változtat azon!1.4.5.2 Balra toló operátor(<<):Ez az operátor a szám bitjeit balra tolja a megadott mértékkel. Egy bittel való eltolás, 2-vel való szorzásnak felel meg. Nézzünk egy példát: 00000000000000000000000000001000//8<<200000000000000000000000000100000//32[/quote]Itt megint könnyedén észrevehetõ mi is történt. Minden bitet fentebb \"toltunk\" 2-vel. Azok a bitek, amelyek ezáltal a 32. bit elé kerültek, kiesnek. Az így megüresedett bitek 0-kkal egészültek ki. Eddig nem túl bonyolult. Azonban figyeljünk oda, ha túl sokáig shiftelünk egy pozitív számot, akkor az utolsó érték, melyet fel tud venni a változónk (mielõtt még túlcsordulás miatt elérné a 0-t), az egy negatív szám!Nézzük mi történik negatív számok esetén: 11111111111111111111111111111000//-8<<211111111111111111111111111100000//-32[/quote]Láthatjuk hogy a balra shifteléskor a szám mindig 0-kkal egészítõdik ki, legyen az akár negatív, akár pozitív. Nem veszi figyelembe az MSB értékét, így ha túl sokat shiftelünk, akkor elõfordulhat, hogy a szám elveszti negatív értékét, és eléri a 0-t.1.4.6 Logikai biteltolás (logical shift, logikai shiftelés) operátorElõször is le kell tisztázni, hogy logikai shiftelésbõl csak egy létezik, jobbra logikai shiftelés(>>>). Sokban hasonlít a jobbra shiftelõ operátorra (>>), azonban van egy fontos különbség.Nézzük az elsõ példát: 00000000000000000000000000001000//8>>>200000000000000000000000000000010//2[/quote]Eddig nem igazán tapasztalhattunk semmi változást, minden pont ugyanúgy zajlott, mint a jobbra shiftelésnél(>>).Nézzük mi történik negatív szám esetén: 11111111111111111111111111111000//-8>>>200111111111111111111111111111110 //1073741822[/quote]Itt már láthatjuk a különbséget. Logikai shiftelés esetén az üres bitek helyére bekerülõ érték nem függ az MSB-tõl, hanem mindig 0. Emiatt soha nem kaphatunk eredményül negatív számot, logikai shiftelés esetén.1.5 Elsõ fejezet végeNos, ezennel átvettünk mindent, amit érdemes tudni a bitmûveletekrõl. Megmutattam az alapok, hogy hogyan épülnek fel a változók, valamint átvettük a fõ operátorokat. Eddig csak színtiszta elmélet volt. A következõ részben megmutatom, hogyan hasznosíthatjuk mindezt a gyakorlatban, vagyis mire is jó az a sok marhaság, amit eddig leírtam.2. Gyakorlati példák, érdekességek 2.1 Áttekintés, mikrõl is lesz még szóAz alábbiakban még pár dolgot szeretnék mutatni. Elõször is néhány \"függvénnyel\" fogok foglalkozni, majd mutatok még egy-két érdekességet. Konkrétan megmutatom, hogyan tudjuk egy jármû minden \"paneljének\" adatát kiíratni, részletesen(erre alapból nincs lehetõség, és a függvények is mind \"encode\" függvények, én viszont mutatok egy-egy decode \"verziót\" is.)Az alábbi 4 függvénnyel fogok foglalkozni: encode_tiresencode_panelsencode_doorsencode_lightsDe elõbb egy kis háttérinfó:A Pawnban a forrásban megadhatunk bináris számokat ugyanúgy, ahogyan Hex számokat is megadhatunk. Ehhez a \"0b\" prefixet kell használnunk. Tehát ha bináris számunk az 1011 és ezt akarjuk adni értékül egy változónak, azt így tehetjük meg: new var = 0b1011; 2.2 encode_tiresHáttér információ:A SA:MP-ban egy jármû abroncsainak adataid 4 bit tárolja. Az elsõ bit a jobb hátsó, a második bit a jobb elsõ, a harmadik bit a bal hátsó, a negyedik bit pedig a bal elsõ abroncsról tárol információt. Mivel 4 bitben eltárolhatóak az információk, így decimális formában 0-15 értékekkel is be lehet állítani a jármûvek abroncsait. 16-tól kezdve az értékek már ismétlõdni kezdenek, szóval a 16-nál az eredmény ugyanaz mint 0-nál, 17-nél ugyanaz mind 1-nél, és így tovább. Ez érthetõ, ha tudjuk hogyan épül fel a kettes számrendszer.(Ne felejtsük el, a biteket hátulról számozzuk!)Nos nézzük hogyan néz ki a kód: encode_tires(tire1, tire2, tire3, tire4){ return tire1 | tire2 << 1 | tire3 << 2 | tire4 << 3;} Láthatjuk, hogy bekérjük a 4 abroncs adatát(vagyis hogy ép[0], vagy kidurrant[1]), majd egy sima bitenkénti VAGY mûvelet és biteltolás kombinálásával visszatérünk az eredménnyel. A kódban a tire1 felel meg a jobb hátsó abroncsnak, a tire2 a jobb elsõ, a tire3 a bal hátsó, és a tire4 a bal elsõnek. Nézzünk egy konkrét példát. Tegyük fel, hogy én a hátsó 2 abroncsot ki akarom durrantani, még az elsõ kettõt épre akarom beállítani. Ez esetben így néz ki a dolog: encode_tire(1,0,1,0); Ha kiíratnánk konzolba a visszatérési értéket (%d-vel), 5-öt kapnánk. Miért is? Az 5 bináris alakja 0101. Hoppá, ha megnézzük ez pont fordítottja annak, amit mi megadtunk számjegyekként, csak már konkrét bináris számként. Nézzük hogyan is történhetett mindez, mit is csináltunk. A függvény a következõ mûveletet végzi: tire1 | tire2 << 1 | tire3 << 2 | tire4 << 3 Konyhanyelven úgy magyarázhatnánk el a dolgot, hogy ez a kód egymás mellé pakolgatja a megadott számokat, fordított sorrendben. Na de mi most nem a konyhanyelv miatt vagyunk ebben a leírásban, szóval nézzük a kódunkat. Lényegében ez futott le, a konkrét értékeinkkel nézve: 1 | 0 << 1 | 1 << 2 | 0 << 3 Bármily furcsa is, ez egy mûvelet, melyen szépen végig halad sorban a program. Elõször is írjuk át az egészet bináris alakba(az egyszerûség kedvéért most csak 4 bitet fogok kiírni): 0001 | 0000 << 1 | 0001 << 2 | 0000 << 3 Na erre se mondtuk volna rá a tutorial elsõ részének elolvasása elõtt, hogy ez egy mûveletsor. A rendszer elsõnek az eltolásokat végzi el (olyan ez mint a 2*3+2*4+2*5 mûveletben elõször a szorzásokat végezzük el, és csak utána adunk össze, ha nincs zárójel), és csak utána a bitenkénti VAGY mûveleteket, ezért mi is így fogunk tenni. Emlékezzünk, eltoláskor az \"üres\" bitek 0 értéket fognak felvenni. Végezzük el külön csak az eltolásokat: 0000 << 1 = 00000001 << 2 = 01000000 << 3 0000 Nos, ha ezeket elvégeztük, akkor nézzük, hogy is áll most a kódunk: 0001 | 0000 | 0100 | 0000 Négy azonos mûvelet van hátra, így hát a program innen más szépen sorban fog haladni. Tegyünk mi is így. Elsõ tehát a 0001 | 0000 mûvelet elvégzése. Emlékeztetõül, a bitenkénti VAGY mûvelet akkor ad 0-t eredményül, ha mind a kettõ érték 0, minden más esetben 1-et. Végezzük el külön az elsõ bitenkénti VAGY mûveletet: 0001 | 0000 = 0001 Nem volt bonyolult, nézzük tehát hogy áll akkor most a teljes mûveletünk: 0001 | 0100 | 0000 Haladunk szépen tovább, elvégezzük a 0001 | 0100 mûveletet. 0001 | 0100 = 0101 Nincs mit magyarázni rajta, egyszerû bitenkénti VAGY mûvelet volt ez is. Már csak ez van hátra: 0101 | 0000 Nos, ez sem lesz egy bonyolult mûvelet: 0101 | 0000 = 0101 Ezzel meg is kaptuk a végeredményt, ami nem más, mint a 0101 (tízes számrendszerben 5). Mivel azonban eléggé kusza lett így a számításunk, nézzük egyben hogyan is néz ki az egész, ha nem bontom ennyire részekre: 0100 | 0000 << 1 | 0001 << 2 | 0000 << 3v0001 | 0000 | 0100 | 0000v0001 | 0100 | 0000v0101 | 0000v0101 Nos, ezennel láthattuk a gyakorlatban is, hogyan mûködik a függvény. Kijelenthetjük tehát, hogy a függvény a számokat a sorrendjüknek megfelelõ számú bitbe helyezi. Mivel azonban a biteket hátulról számozzuk, így az 1,0,1,0 bemenetek végeredménye 0101 lesz. Így már nem is olyan bonyolult Na, ha már ilyen sokáig eljutottunk, akkor felhasználhatjuk ezt a kódot arra, hogy az alapjait figyelembe véve elkészítsük a decode változatot. Mivel 4 értékkel térünk vissza, ezért paraméterként kell majd 4 változó, melyeknek módosíthatjuk az értékét, illetve 1 paraméter pedig a dekódolandó szám. A függvényünk \"címsora\" tehát így néz ki: decode_tires(tires, &tire1, &tire2, &tire3, &tire4) Hogy példánk ne csak elmélet legyen, a tires változó legyen az elõbbi 0101 értékkel egyenlõ. Na akkor menjünk tovább. Bemeneti értékünk ugye a \"tires\" változóban lesz eltárolva, így ezzel fogjuk a mûveleteket végezni. Kezdjünk el gondolkozni. Hogyan is tudnánk ellenõrizni egy adatot. Legegyszerûbb ha a bitenkénti ÉS mûveletet alkalmazzuk. Miért is? Nos, a bitenkénti ÉS mûvelet ugye akkor ad eredményül 1-et, ha mind a 2 érték egy. Tehát ha min egy számot összehasonlítunk a 1-el(binárisan ugye 00000000000000000000000000000001, akkor az eredményünk lényegében arról fog tájékoztatni, hogy az elsõ bit 0, vagy 1. Nem is olyan bonyolult ez. Nézzük akkor az elsõ bitet. Arra emlékezhetünk még, hogy az elsõ bit a tire1, vagyis jobb hátsó abroncs adatait tárolja. Tehát: tire1 = tires & 1; Vagy konkrétan a példánk esetében: tire1 = 0101 & 0001 Mivel a bitenkénti ÉS mûvelet akkor ad 1-et eredményül, ha mind a kettõ bit 1, akkor már láthatjuk is, hogy miért elegendõ az elsõ bitet szemügyre venni. Mivel mind a 2 helyen az elsõ bit 1, ezért a tire1 változónk értéke is 1(0001) lesz. Egyszerû és nagyszerû, máris megkaptuk a jobb hátsó abroncs állapotát.Gondolkozzunk hát tovább. Nekünk most arra van szükségünk, hogy a 2. bit értékét megkapjuk. Hogyan kaphatjuk ezt meg? Legegyszerûbb megoldás, ha a tires változót jobbra shifteljük 1-el, majd ismételten bitenkénti ÉS mûveletet végzünk. Nézzük tehát, hogyan is nézne ez ki: tire2= tires >> 1 & 1; Vagy konkrétan a mi példánkban: tire2 = 0101 >> 1 & 0001vtire2 = 0010 & 0001 Ismételten bitenkénti ÉS mûvelet, és az eredményünk 0, mivel csak az egyik bit 1, a másik 0, így a bitenkénti ÉS mûvelet eredménye is 0. Ezen az elven megcsináljuk a maradék 2 abronccsal is: tire3= tires >> 2 & 1;tire4= tires >> 3 & 1; Vagy a mi esetünkben konkrét példánkkal: tire3 = 0101 >> 2 & 0001 // azaz: --> 0001 & 0001 = 1tire4 = 0101 >> 3 & 0001 // azaz: --> 0000 & 0001 = 0 A teljes kódunk tehát: decode_tires(tires, &tire1, &tire2, &tire3, &tire4){ tire1 = tires & 1; tire2 = tires >> 1 & 1; tire3 = tires >> 2 & 1; tire4 = tires >> 3 & 1;} A konkrét példánkban a 4 változónk értéke a függvény lefutása után: tire1: 1tire2: 0tire3: 1tire4: 0 Ha összehasonlítjuk, ezek pont azok az értékek, mint amiket az encode függvényben megadtunk, tehát úgy tûnik jól dolgoztunk 2.3 encode_lightsEz a rész lesz a legrövidebb. Az egész lámpás rész pontosan ugyanarra a sémára épül mint az abroncsok. Az adott bitek ugyanazon a \"helyen\" lévõ lámpa adatait szolgáltatják, ugyanúgy 0 az ép, és 1 a sérült lámpák állapot azonosítója, és ugyanúgy 1-1 bit tárolja az értékeket.Maga a kód: encode_lights(light1, light2, light3, light4){ return light1 | light2 << 1 | light3 << 2 | light4 << 3;} Láthatjuk hogy halál pontosan ugyanaz mint az encode_tires, csak más változónevekkel. Így hát a decode függvény is ugyan az lesz, csak a változóneveket kell módosítanunk: decode_lights(lights, &light1, &light2, &light3, &light4){ light1 = lights & 1; light2 = lights >> 1 & 1; light3 = lights >> 2 & 1; light4 = lights >> 3 & 1;} Ha megnézzük látjuk, hogy teljesen ugyan az a séma, ugyan az a felépítés, minden megegyezik, ezért nem is írtam le újból ugyanazt, amit egyszer már kifejtettem.2.4 encode_panelsHáttér információ:A SA:MP 7 panelrõl tárol adatokat. Egy panel adatait 4 bit írja le. Nos, ennél több infóra nekünk most nincs szükségünk, az elõzõ kód alapján ezen kód is megérthetõ, így az encode verzióhoz nem is nagyon szeretnék magyarázatot fûzni. A különbség az encode_tires-hez képest, hogy itt 4-essével shiftelünk, mivel 1 panel adatait 4 bit tárolja.Maga a kód: encode_panels(flp, frp, rlp, rrp, windshield, front_bumper, rear_bumper){ return flp | frp << 4 | rlp << 8 | rrp << 12 | windshield << 16 | front_bumper << 20 | rear_bumper << 24;} Nézzünk egy decode verziót, ehhez a kódhoz. Alapjaiban ugyanazon a vonalon kell elindulnunk, mint a decode_tires esetében. Itt most 7 visszatérési értékünk lesz, és 1 bemeneti érték. Tehát így néz ki a függvény header: decode_panels(panels, &flp, &frp, &rlp, &rrp, &windshield, &front_bumper, &rear_bumper) A folytatásban megint csak az elõbbi verziót kell alapul vennünk, azonban itt lesz egy kis különbség. Egyértelmû, hogy mivel 1 panel adatait 4 bit tárolja, így a 4 bitet nekünk egyben kell lekérni, és egyben kell vele visszatérnünk. Megint csak a bitenkénti ÉS mûveletet fogjuk alkalmazni, azonban mivel 4 bitrõl van szó, így egy olyan számmal kell összehasonlítanunk, mely bináris alakban 1111. Ez a szám nem más, mint a 15. Hogy miért ez kell?Ha egy bitsorozat tartalmát ki szeretnénk másolni egy másik változóba, akkor a bitenkénti ÉS mûveletre vna szükségünk. A bitenkénti ÉS mûvelet akkor ad 1-et eredményül, ha mindkét bit 1, ha valamelyik 0, akkor a visszatérés már 0 lesz. Ergó tehát, ha egy csupa 1-esekbõl álló bitsorozattal vetjük össze az eredeti bitsorozatunkat, akkor lényegében lemásoljuk a sorozatot, egy másik változóba, hisz eredményül mindig az eredeti sozatot fogjuk visszakapni. Íme néhány példa: 0101 & 1111 = 01011111 & 1111 = 11110000 & 1111 = 00001011 & 1111 = 1011 Láthatjuk tehát, hogy ha csupa 1-est tartalmazó bitsorozattal hasonlítjuk össze az eredeti számunkat, akkor lényegében egy másolatot készítünk. Mivel nekünk erre a 4 bitre van szükségünk a 32-bõl, így pontosan ezt kell tennünk, ezen 4 bitet lemásolni egy másik változóba, hogy késõbb ezt majd egy másik függvénnyel még jobban darabokra szedhessük.Íme tehát akkor a kódunk elsõ sora: flp = panels & 15; Mivel 4-esével vizsgáljuk a biteket, így lényegében annyit kell még változtatnunk az elõzõ kódon(decode_tires), hogy az 1-esével növekvõ shiftelések helyett 4-esével shifteljünk. Azaz a 2. panel adatainak lekérdezése: frp = panels >> 4 & 15; Innen pedig már csak mindig további 4 shiftelést el kell végeznünk, és szépen végig haladhatunk a panels változó bitjein, és megkaphatjuk minden panel adatait külön-külön. rlp = panels >> 8 & 15;rrp = panels >> 12 & 15;windshield = panels >> 16 & 15;front_bumper= panels >> 20 & 15;rear_bumper = panels >> 24 & 15; Ezennel minden panel adatait megkaptuk. Ezután már akár a decode_tires függvényt felhasználva szét lehet szedni az adatokat darabokra, de akár egy saját névvel, és más változó nevekkel ellátott másik függvényt is létre lehet hozni a decode_tires mintájára.Tehát az egész kód egyben: decode_panels(panels, &flp, &frp, &rlp, &rrp, &windshield, &front_bumper, &rear_bumper){ flp = panels & 15; frp = panels >> 4 & 15; rlp = panels >> 8 & 15; rrp = panels >> 12 & 15; windshield = panels >> 16 & 15; front_bumper= panels >> 20 & 15; rear_bumper = panels >> 24 & 15;} Igazából a függvény ugyanarra a sémára épül mint a decode_tires, csak annyi a különbség, hogy 4 bitenként kapjuk az adatokat, nem pedig egyesével, mivel itt minden panelnek 4 adatát tudjuk majd még lekérdezni. Nem tudom lehet-e még ehhez a részhez hozzáfûzni valamit, szóval szerintem ugorjunk. 2.5 encode_doorsHáttér információ:A SA:MP képes minden jármû 4 \"ajtajának\" állapotát tárolni. Ezek az \"ajtók\"(Doors-t hogy fordíthatnám jobban?) konkrétan a motorháztetõ, csomagtartó, vezetõoldali ajtó, és anyósülés oldali ajtó. A 2 hátsó ajtó állapotát jelenleg még nincs lehetõségünk lekérdezni.Amikor lekérdezzük egy jármû \"ajtajának\" adatait, azt a SA:MP 4 bájtként(32 bit) adja vissza. Ebbõl a 4 bájtból 1 bájt(8 bit) jut minden egyes objektumra. Vagyis minden egyes objektum adatait 8 bit tárolja. Vagyis tárolhatná...A SA:MP csak az elsõ 3 bitet használja, a további bitek üresek lesznek, nincs funkciójuk, talán majd késõbb lesz. Az elsõ bit azt tárolja, hogy az objektum zárt[0], vagy nyitott[1](nem az jelenti, hogy bezárt, hanem becsukott/kinyitott) állapotban van-e. A második bit azt adja meg, hogy az objektum ép[0] vagy sérült[1]. A 3. bit arról tájékoztat, hogy az adott objektum rajta van-e még a jármûvön[0] vagy már leesett[1]. A többi bit mint mondtam üres. Ez majd a decode függvénynél lesz fontos.Nézzük tehát a konkrét kódot: encode_doors(bonnet, boot, driver_door, passenger_door, behind_driver_door, behind_passenger_door){ #pragma unused behind_driver_door #pragma unused behind_passenger_door return bonnet | boot << 8 | driver_door << 16 | passenger_door << 24;} A 2 hátsó ajtóval nem foglalkozunk, mivel azok jelenleg még nem támogatottak ugyebár, de a kód készítõje gondolt a jövõre is, vagy fogalmam sincs miért adta meg ezt a két paramétert is, hisz ígyis-úgyis módosítani kell a kódon... Ha behoznak még 2 ajtót, akkor az 1 bájtos tárolással már 6 bájt lenne, de 1 változó maximum 4 bájt lehet, tehát a kódot úgyis módosítani kell, ha meg arra gondoltak, hogy legalább a paramétereket nem kell majd szegény scriptereknek minden sorban átírni, az megint hülyeség, hisz ki adna értéket olyan paraméternek, amely nincs használva?...Nos mindegy, nem a kritizálásért vagyunk most itt. Ha megnézzük, a kód 8-as shiftelést használ, mivel egy objektum adatait 8 bit tárolja. A mûködési elv ugyanaz, mint az elõzõ encode függvényeknél, így erre nem kívánok megint kitérni.Nézzük azonban hogyan írnánk ehhez egy decode függvényt. Mikor megkérdeztem errõl ismerõsömet, õ azt mondta, ugyanúgy mint az elõzõ, decode_panels függvénynél, csak 8-as shiftelést használunk, és 15 helyett 255-el végezzük a bitenkénti ÉS mûveletet. 255-el? minek?No erre is ki fogok térni késõbb, de haladjunk a szokásos módon. 1 bemeneti paraméterünk van, és 4 kimeneti, vagyis a header rész a következõ: decode_doors(doors, &bonnet, &boot, &driver_door, &passenger_door) Ezzel meg is vagyunk, itt nem látok különösebb magyarázni valót. Én most nem fogok törõdni a 2 nem létezõ ajtóval, ha egy jövõbeni verzióban netán azokat is behoznák mint lekérdezhetõ objektum, akkor ígyis-úgyis módosítani kell majd a kódot, addig meg úgysincs rá szükségünk.Nos, mint az eddigi decode függvényeknél, elsõ feladatunk egy shiftelés mentes bitenkénti ÉS mûvelet elvégzése. Az egyik megoldás a következõ: bonnet = doors & 255; Lehet így is. Azonban véleményem szerint felesleges itt egy ekkora számmal \"szenvednünk\". Miért használjunk ekkora számot, és ezáltal bonyolítsuk feleslegesen a kódunkat, ha nekünk a 8 bitbõl csak az elsõ 3 lesz ami adatot tárol? Bõven elegendõ egy olyan szám, amely bináris alakja 111. Ez nem más, mint a 7. Tehát a következõ megoldás ugyanúgy helyes: bonnet = doors & 7; A két kód pontosan ugyanazt az eredményt fogja visszaadni, semmi különbség a kettõ között. Csupán elsõ esetében nagy számmal dolgozunk, még második verzió esetén csak akkorával, amekkora szükséges.A kód többi részében csak 8-as shifteléssel végigcammogunk a doors változó többi bitjén is: boot = doors >> 8 & 7;driver_door = doors >> 16 & 7;passenger_door = doors >> 24 & 7; A teljes kód egyben: decode_doors(doors, &bonnet, &boot, &driver_door, &passenger_door){ bonnet = doors & 7; boot = doors >> 8 & 7; driver_door = doors >> 16 & 7; passenger_door = doors >> 24 & 7;} A kód pont ugyanazon az elven épül fel, mint az összes többi decode függvényünk, csak épp a benne használatos értékek az \"ajtó\" objectumok adatstruktúrájához vannak igazítva.2.6 Egyéb ötletek gyakorlati alkalmazásra, hasznosításraJoggal felmerülhet a kérdés, hogy ennyi? Emiatt rágtuk végig magunkat ezen néhány tízezer karakteren? Nem. Nem ennyi. A SA:MP-ban rengeteg felhasználási módja lehet még a bitmûveleteknek. Mutatok is néhány dolgot, mire jók még.2.6.1 RGBA->HEX; HEX->RGBA konvertálásokNos, bármily \"hihetetlen\", erre is használható. Miért is? Nos, 16-os számrendszerben 0-9, valamint A-F \"szánjegyek\" léteznek. A hexadecimális számjegy decimális értékben 0-15 lehet. Ha megnézzük bináris értékeket, 0-15 az 4 bittel leírható, hiszen a 15 az bináris alakban 1111. De ha józan paraszti ésszel belegondolunk is. 16 az 24. Így tehát 1 hexadecimális érték, az 4 bináris értékkel írható le. Ebbõl már sejthetõ, hogy itt is shiftelésre lesz szükségünk. Naná Mivel 4 bit ír le egy hexadecimális számjegyet, így 4-esével kell majd shiftelnünk.Mi is az az RGBA?RGBA: A színek olyan meghatározási módja, melyben a 3 alapszín([r]ed, [g]reen, lue) mellett egy 4. érték, az úgynevezett [a]lpha is szerepel, amely az áttetszõséget adja meg. A legtöbb program(pl. paint, PS), és pl. a css(külön függvénnyel!) is elfogadja ezen értékeket decimális számokként(0-255) is, a SA:MP-ban azonban hexadecimális alakban kell megadnunk. Bármily furcsa a következõ 4 sor pont ugyan azt jelenti:\"Normál\" RGB alak(pl. paint, photoshop): Piros: 0, Zöld: 255, Kék: 255CSS alak(függvénnyel): rgba(0, 255, 255, 1);Pawn alak(1): 0x00FFFFFFPawn alak(2): 16777215 Ó igen, a Pawn-ban decimális számokként is megadhatjuk a színeket, hisz a program mindent bináris számrendszerbe fog átszámolni. De akár bináris alakban is megadhattuk volna:Pawn alak(3): 0b111111111111111111111111 Az, hogy mi a Pawn-nak hexadecimális alakban szoktuk megadni, annak az egyetlen oka, hogy így a leginkább \"átlátható\", és ez egy amúgy is elfogadott módja (mármint a hexadecimális számmal való megadás) a színek leírásának a számítástechnikában.Nos, mivel 1 szín 0 és 255 közötti értéket tartalmaz a piros, zöld és kék színekbõl, ez hexadecimálisan 2 számjeggyel leírható: 00-FF. Az FF értéke 255, aki nem hiszi, számoljon utána (162-1)2 hexadecimális számjegy az pedig 8 bináris számjeggyel leírható(00000000-11111111). Így már láthatjuk, hogy 8-as shiftelésre lesz szükségünk. Másra nincs is szükségünk, az RGBA->HEX konverter elkészítéséhez.stock RGBAToHex( r, g, b, a ){return a | b << 8 | g << 16 | r << 24;} Az, hogy miért fordított sorrendben kellett megadnunk az értékeket, az megint csak azzal magyarázható, hogy a biteket hétköznapi értelemben véve visszafelé számozzuk.Egy ellentét, azaz HexToRGBA függvény megírása sem túl bonyolult, ugyan ezt kell csinálnunk, csak visszafelé.stock HexToRGBA( colour, &r, &g, &b, &a ){a = colour & 0xFF;b = (colour >> 8 ) & 0xFF;g = (colour >> 16 ) & 0xFF;r = (colour >> 24 ) & 0xFF;} A 0xFF-et természetesen decimális értékkel is megadhatjuk(255).No akkor lehet ugye RGBA-t ARGB-be is konvertálni, az így néz ki:RGBAToARGBHEX(r,g,b,a){return (r & 0xff) << 24 | (g & 0xff) << 16 | (b & 0xff) << 8 | a & 0xff;} Ez máris egy újabb felhasználási módja a bitmûveleteknek. De, mutatok még 2.6.2 Boolen változók helyett IntegerAz al-al-alcímet elolvasva joggal felmerülhet a kérdés, hogy ez meg hogy jön ide. Nos ez úgy jön ide, hogy most egy kis memóriatakarékosságról fogok itt regélni. Adhattam volna azt is címnek, hogy miért ne használjunk boolean változókat, ha legalább 5 igaz/hamis értéket el akarunk tárolni.Nos akkor egy kis memórialecke. Azt már tisztáztuk, hogy az integer változók 32 bitesek, vagyis 4 bájtot foglalnak le a memóriában maguknak. A boolean típusú változókról azonban még nem volt szó. Mivel 1 értéket tárolnak csak, az is csak igaz vagy hamis lehet, így joggal gondolhatnánk, hogy 1 bit a mérete. Azonban nem...1 boolean változó 8 bitet, azaz 1 bájtot foglal le magának a memóriában. Hogy ez miért így van, az a számítógépek memóriahasználatával magyarázható. Manapság nincs mód arra, hogy a memóriában egyetlen bitet módosítsunk, csak 8-as \"csomagokban\", vagyis bájtonként tudjuk ezt megtenni. Hogy ez miért van így, azt most nem fogom itt fejtegetni Nos, ha megnézzük tehát, a boolean változó egy nagyon-nagyon pazarló megoldás. Hiszen 1 darab boolean változó 1 bájtot foglal, 4 darab boolean változó esetén ez már 4 bájt, ami megegyezik 1 integer változó méretével. Azonban, még a 4 boolean változóban 4 igaz/hamis érték kerülhet tárolásra, addig egy integerben 32 igaz/hamis értéket is eltárolhatunk.Bizony, 32 igaz/hamis érték. Hiszen 1 integer változó 32 bitbõl áll, ha minden bitet hasznosítunk, akkor a bitmûveletek felhasználásával 32 igaz/hamis értéket is eltárolhatunk. Összehasonlítva:32 darab boolean változó mérete 32 bájt.1 integer változó mérete: 4 bájt. Vagyis 32 boolean változó esetén nyolcad annyi memóriánkba kerül, ha egy darab integerben tároljuk el az értékeket. Persze 32 booleanra ritkán van szükség, de egy RP modban simán elõfordulhat. Gondoljunk csak bele... Van 32 boolean-tömbös változónk, ez a maximum slotszámot(1000) figyelembe véve: 32*1000 byte azaz 32000 byte ami ~31 kilobyte. Ha ezt egyetlen integer-tömbbel oldanánk meg: 4*1000 byte, azaz 4000 byte, ami ~3,9 kilobyte. Azért így nézve már nem annyira \"elhanyagolandó\" az a különbség szerintem =)Akár írhatnánk erre külön egy függvényt, mely az adott bit értékével tér vissza. Nem is bonyolult. Így már azok számára is használhatóak a bitmûveletek, akik nem értenek hozzá, és rengeteg memóriát takaríthatnak meg.Íme egy függvény az adatok kiolvasására:stock GetData(integer, bit){ return integer >> bit & 1; //vagy az 1 helyett akár 0x01 alakban is megadhatjuk, nincs jelentõsége} És a párja, adat írásra:stock SetData(&integer, bit, value){ integer |= value << bit; return 1;} Persze ezt még lehetne csinosítani, hogy pl. ne 0-tól hanem 1-tõl számoljuk a biteket, vagy hogy 31(vagy 32 ha 1-tõl számolunk) felett 0 legyen a return értéke, esetleg a value-re is rakhatnánk ellenõrzést, hogy 0-1-e az értéke, vagy más, de ilyenekkel most nem törõdtem.2.6.3 Több érték, egy integerbeNa ez az a rész, ami véleményem szerint már csak a nagyon megrögzött memóriafelszabadítók szerint nem lesz felesleges Amennyiben nincs szükségünk túl nagy számokra(pl. csak 0-15, vagy akár csak 0-7, de akár még a 0-255 is megfelel), akkor akár egy integerbe is tehetjük õket. Hogyan? Egyszerû. Csoportosítjuk a biteket. Tegyük fel, hogy maradván egy RP szervernél, el akarjuk tárolni ûbergigantikusan memóriakímélõ megoldással a játékos házának számát(ez legyen mondjuk 0-100 közötti szám), a játékos frakcióját(ez legyen 0-15 között), a játékos rangját(0-7 között), hogy a telefonja be van-e kapcsolva(0-1), a játékos szolgálati jármûvének id-jét(legyen 0-255 között), és az alapmunkáját(0-15 között). Ez is megoldható, egyetlen integerben, és ismételten 1000 slottal számolva máris megspóroltunk 4*4*1000+1*1000 byte-t(5 integer, és 1 boolean helyett csupán 1 integert használunk), ami ~16,6 kilobyte. Azért nem mindegy az, hogy 20,5 kb-ot vagy 4 kb-ot használunk szerintem... Ezt a következõ módszerrel valósíthatnánk meg:#define DATA_HOUSE 0#define DATA_FACTION 1#define DATA_RANK 2#define DATA_PHONESTATE 3#define DATA_DUTYCAR 4#define DATA_JOB 5stock GetData(playerinfo, data){ switch(data) { case DATA_HOUSE: return playerinfo & 127;//7 bit case DATA_FACTION: return playerinfo >> 7 & 15;//4 bit case DATA_RANK: return playerinfo >> 11 & 7;//3 bit case DATA_PHONESTATE: return playerinfo >> 14 & 1;//1 bit case DATA_DUTYCAR: return playerinfo >> 15 & 255;//8 bit case DATA_JOB: return playerinfo >> 23 & 15;//4 bit}return -1;}stock SetData(&playerinfo, data, value){ switch(data) { case DATA_HOUSE: { playerinfo = playerinfo & ~127 | value; }//7 bit (0-127 közötti maximálisan) case DATA_FACTION: { playerinfo = playerinfo & ~(15 << 7) | value << 7; }//4 bit (0-15 között) case DATA_RANK: { playerinfo = playerinfo & ~(7 << 11) | value << 11; }//3 bit (0-7 között) case DATA_PHONESTATE: { playerinfo = playerinfo & ~(1 << 14) | value << 14; }//1 bit (0-1) case DATA_DUTYCAR: { playerinfo = playerinfo & ~(255 << 15) | value << 15; }//8 bit (0-255 között) case DATA_JOB: { playerinfo = playerinfo & ~(15 << 23) | value << 23; }//4 bit (0-15 között) } return -1;}stock SetDatas(&playerinfo, house, faction, rank, phonestate, dutycar, job){playerinfo |= house | (faction << 7) | (rank << 11) | (phonestate << 14) | (dutycar << 15) | (job << 23);} Ez már azért elég morbid, ezt inkább csak érdekességképp mutattam, de éppenséggel ez is használható lenne, csak ehhez már kell egy kis õrültség 2.7 Második fejezet végeNos, láthattuk hogyan is mûködik mindazon sok \"marhaság\", amit az elsõ fejezetben elméletként átvettünk. Láthattuk hogyan mûködnek a jármûvek paneljének adataival babráló encode függvények, valamint megmutattam, hogyan írhatunk hozzájuk decode verziókat. Láthattunk még példát egyéb gyakorlati hasznosításra, kicsit megkoccoltuk a hexadecimális számok világát is, majd szóba került egy kis memóriatakarékosság. Végül mutattam egy összetettebb, elvetemült példát is, a bitmûveletek hasznosítására.3. Összegzés, végszó Úgy gondolom, hogy elég részletesen körbe jártuk a témát. Megnéztük a bitmûveletek elméleti, és gyakorlati oldalát is, láthattunk gyakorlati példákat. Nem kell elsõre megérteni ezt az egészet, sõt... Elegendõ ha egyszer elolvassuk, emésztjük kicsit, majd késõbb megint elõvesszük, akkor netán már kicsit többet is megértünk belõle. Nem egy kihagyhatatlan része a Pawno-nak, de azért van annak haszna, ha az ember jártas ebben a témában is, bár sokak szerint nem éri meg a sok fáradtságot, és idõt, mely a megértéséhez, begyakorlásához, használatának elsajátításához szükséges. Próbáltam mindent részletesen kifejteni. A bonyolultabb dolgokhoz sokat magyarázni, az egyszerûbbek esetén csak visszautalni az alapokhoz. Sok dolgot talán már túl is magyaráztam. Ezt a leírást nem 1 nap alatt készítettem el, kb. 1 hónapja írtam meg az elméleti oldalát, most az elmúlt pár napban pedig a gyakorlatot. Esténként 1-2 órát szántam rá csupán, és mivel ilyenkor már nem vagyok épp a toppon, elképzelhetõ, hogy néhány helyen helyesírási, vagy elvi hibát vétettem. Amennyiben valaki ilyet találna, kérem szóljon, és korrigálni fogom. Nos, azt hiszem nincs is más hátra, mint hogy sok szerencsét kívánjak a bitmûveletek elsajátításához, és megosszam a forrásokat. Remélem néhányan hasznosnak találják, eme igen hosszúra sikerült leírást.4. Források: Internet:WikipédiaSA:MP WikiSA:MP hivatalos fórumKönyv, e-book:Pohl László - A programozás alapjai(BME - Elektronikus Eszközök Tanszéke, Budapest, 2010)Siroki László - C programozás - Bitmûveletek jegyzeteˆ Anthony, 2012 - Minden jog fenntartva.A leírás egészben, vagy részleteiben szabadon terjeszthetõ, ameddig egyértelmûen fel van tüntetve az eredeti készítõ, valamint hogy honnan is származik. « Utoljára szerkesztve: 2012. augusztus 01. - 10:53:54 írta Anthony » Naplózva
A B A^B000101011110[/quote]Példának nézzük meg, mi történik, ha a bitenkénti KIZÁRÓ VAGY operátort alkalmazzuk, szintén a 11 és a 7 esetén. A 11 bináris számrendszerben mint már írtam 00000000000000000000000000001011-nek felel meg, a 7 pedig 00000000000000000000000000000111-nek. Tehát a 11 ^ 7, az ugyaz mint ha ezt írnám: 00000000000000000000000000001011 ^ 00000000000000000000000000000111. Az átláthatóság kedvéért ismét egymás alá fogom írni a 2 számot, hogy áttekinthetõbb legyen, és az eredményt pedig a 3. sorba: 00000000000000000000000000001011^0000000000000000000000000000011100000000000000000000000000001100[/quote]Eredményül most 00000000000000000000000000001100-t kaptunk, ami a kettes számrendszerben a 12-nek felel meg. Érdekes, hogy teljesen más eredményt kapunk, pedig lényegében ugyanazt az eredményt adja az operátor, - kis különbséggel ugyebár - mint a VAGY operátor.A következõ 2 sor tehát ugyanazt jelenti: Bin:00000000000000000000000000001011^00000000000000000000000000000111=00000000000000000000000000001100Dec:11^7=12[/quote]Megint néhány példa: 00000000000000000000000000000011 ^ 00000000000000000000000000000101 = 0000000000000000000000000000011000000000000000000000000001110110 ^ 00000000000000000000000001011001 = 00000000000000000000000000101111[/quote]Ezzel kapcsolatban egy érdekességet mutatnék meg. Vegyünk 2 számot. A látványosság kedvéért én példának a 118-at és a 89-et veszem. 1. szám: 00000000000000000000000001110110 = 1182. szám: 00000000000000000000000001011001 = 89Mûvelet elvégzése: 00000000000000000000000001110110 ^ 00000000000000000000000001011001 = 00000000000000000000000000101111 = 471. szám az eredmény, 2. szám az eredeti 2. szám: 00000000000000000000000001011001 = 89Eredmény: 00000000000000000000000000101111 ^ 00000000000000000000000001011001 = 00000000000000000000000001110110 = 118[/quote]Vagyis visszakaptuk a kiindulási állapotot 1.4.4 Bitenkénti (bitek közötti) NEGÁLÁSA bitenkénti NEGÁLÁS (NOT, ~) az utolsó logikai operátor, melyet meg fogok mutatni. Ez az operátor eléggé eltér az eddigiektõl, leginkább abban, hogy ezt a mûveletet nem 2 bitre alkalmazzuk, hanem csak 1-re. Szokás komplementer-operátornak is hívni(konyhanyelven). Lényegében az 1-esekbõl 0-t, a 0-kból 1-est csinál. Igazság táblázata: A ~A0110[/quote]Példának nézzük meg, mi történik, ha a bitenkénti NEGÁLÁST alkalmazzuk a 7-es számra. A 7 bináris számrendszerben mint már írtam 00000000000000000000000000000111-nek felel meg. Tehát a ~7, az ugyaz mint ha ezt írnám: ~00000000000000000000000000000111. Nézzük tehát, mi történik: ~0000000000000000000000000000011111111111111111111111111111111000[/quote]Eredményül most 11111111111111111111111111111000-t kaptunk, ami a kettes számrendszerben a -8-nak felel meg. A következõ 2 sor tehát ugyanazt jelenti: Bin:~00000000000000000000000000000111=11111111111111111111111111111000Dec:~7=-8[/quote]Megint néhány példa: ~00000000000000000000000000000101 = 11111111111111111111111111111011~00000000000000000000000001110110= 11111111111111111111111110001010[/quote]Ehhez kapcsolódóan lenne itt egy fontos megjegyzendõ dolog. Aki nem érti, hogy miért -8 a megoldás, az nem olvasta el elég rendesen a \"Részletesebben a bitekrõl a Pawn-ban\" részt.1.4.5 Biteltolás (arithmetic shift, shiftelés) operátor(ok)A shift operátorok(<<, >>) a bitek tologatására használhatóak. A << balra tol, a >> jobbra. Bal oldalt található a szám, amit tologatni kívánunk, jobb oldalt pedig az, hogy mennyivel szeretnénk eltolni.1.4.5.1 Jobbra toló operátor(>>):Ez az operátor a szám bitjeit jobbra tolja a megadott mértékkel. Egy bittel való eltolás, 2-vel való osztásnak felel meg. Nézzünk egy példát: 00000000000000000000000000001000//8>>200000000000000000000000000000010//2[/quote]Láthatjuk, hogy semmi más nem történt, mint minden bitet 2-vel hátrébb mozgattunk. Az így \"megüresedett\" bitek helyére pedig 0-k kerültek. Lényegében az történt, hogy az utolsó 2 bit kiesett, az így megmaradt biteket 2-vel hátrébb csúsztattuk, majd az elsõ 2, így \"üres\" bit 0 értéket kapott.Nézzünk egy példát negatív számmal: 11111111111111111111111111110000//-8>>211111111111111111111111111111100//-2[/quote]Láthatjuk, hogy itt fontos szerepe van az MSB-nek. Ettõl függ ugyanis, hogy 0-k vagy 1-esek kerülnek a \"megüresedett\" bitek helyére. Fontos tehát megjegyezni, hogy a jobbra shiftelés megtartja a számok elõjelét, nem változtat azon!1.4.5.2 Balra toló operátor(<<):Ez az operátor a szám bitjeit balra tolja a megadott mértékkel. Egy bittel való eltolás, 2-vel való szorzásnak felel meg. Nézzünk egy példát: 00000000000000000000000000001000//8<<200000000000000000000000000100000//32[/quote]Itt megint könnyedén észrevehetõ mi is történt. Minden bitet fentebb \"toltunk\" 2-vel. Azok a bitek, amelyek ezáltal a 32. bit elé kerültek, kiesnek. Az így megüresedett bitek 0-kkal egészültek ki. Eddig nem túl bonyolult. Azonban figyeljünk oda, ha túl sokáig shiftelünk egy pozitív számot, akkor az utolsó érték, melyet fel tud venni a változónk (mielõtt még túlcsordulás miatt elérné a 0-t), az egy negatív szám!Nézzük mi történik negatív számok esetén: 11111111111111111111111111111000//-8<<211111111111111111111111111100000//-32[/quote]Láthatjuk hogy a balra shifteléskor a szám mindig 0-kkal egészítõdik ki, legyen az akár negatív, akár pozitív. Nem veszi figyelembe az MSB értékét, így ha túl sokat shiftelünk, akkor elõfordulhat, hogy a szám elveszti negatív értékét, és eléri a 0-t.1.4.6 Logikai biteltolás (logical shift, logikai shiftelés) operátorElõször is le kell tisztázni, hogy logikai shiftelésbõl csak egy létezik, jobbra logikai shiftelés(>>>). Sokban hasonlít a jobbra shiftelõ operátorra (>>), azonban van egy fontos különbség.Nézzük az elsõ példát: 00000000000000000000000000001000//8>>>200000000000000000000000000000010//2[/quote]Eddig nem igazán tapasztalhattunk semmi változást, minden pont ugyanúgy zajlott, mint a jobbra shiftelésnél(>>).Nézzük mi történik negatív szám esetén: 11111111111111111111111111111000//-8>>>200111111111111111111111111111110 //1073741822[/quote]Itt már láthatjuk a különbséget. Logikai shiftelés esetén az üres bitek helyére bekerülõ érték nem függ az MSB-tõl, hanem mindig 0. Emiatt soha nem kaphatunk eredményül negatív számot, logikai shiftelés esetén.1.5 Elsõ fejezet végeNos, ezennel átvettünk mindent, amit érdemes tudni a bitmûveletekrõl. Megmutattam az alapok, hogy hogyan épülnek fel a változók, valamint átvettük a fõ operátorokat. Eddig csak színtiszta elmélet volt. A következõ részben megmutatom, hogyan hasznosíthatjuk mindezt a gyakorlatban, vagyis mire is jó az a sok marhaság, amit eddig leírtam.2. Gyakorlati példák, érdekességek 2.1 Áttekintés, mikrõl is lesz még szóAz alábbiakban még pár dolgot szeretnék mutatni. Elõször is néhány \"függvénnyel\" fogok foglalkozni, majd mutatok még egy-két érdekességet. Konkrétan megmutatom, hogyan tudjuk egy jármû minden \"paneljének\" adatát kiíratni, részletesen(erre alapból nincs lehetõség, és a függvények is mind \"encode\" függvények, én viszont mutatok egy-egy decode \"verziót\" is.)Az alábbi 4 függvénnyel fogok foglalkozni: encode_tiresencode_panelsencode_doorsencode_lightsDe elõbb egy kis háttérinfó:A Pawnban a forrásban megadhatunk bináris számokat ugyanúgy, ahogyan Hex számokat is megadhatunk. Ehhez a \"0b\" prefixet kell használnunk. Tehát ha bináris számunk az 1011 és ezt akarjuk adni értékül egy változónak, azt így tehetjük meg: new var = 0b1011; 2.2 encode_tiresHáttér információ:A SA:MP-ban egy jármû abroncsainak adataid 4 bit tárolja. Az elsõ bit a jobb hátsó, a második bit a jobb elsõ, a harmadik bit a bal hátsó, a negyedik bit pedig a bal elsõ abroncsról tárol információt. Mivel 4 bitben eltárolhatóak az információk, így decimális formában 0-15 értékekkel is be lehet állítani a jármûvek abroncsait. 16-tól kezdve az értékek már ismétlõdni kezdenek, szóval a 16-nál az eredmény ugyanaz mint 0-nál, 17-nél ugyanaz mind 1-nél, és így tovább. Ez érthetõ, ha tudjuk hogyan épül fel a kettes számrendszer.(Ne felejtsük el, a biteket hátulról számozzuk!)Nos nézzük hogyan néz ki a kód: encode_tires(tire1, tire2, tire3, tire4){ return tire1 | tire2 << 1 | tire3 << 2 | tire4 << 3;} Láthatjuk, hogy bekérjük a 4 abroncs adatát(vagyis hogy ép[0], vagy kidurrant[1]), majd egy sima bitenkénti VAGY mûvelet és biteltolás kombinálásával visszatérünk az eredménnyel. A kódban a tire1 felel meg a jobb hátsó abroncsnak, a tire2 a jobb elsõ, a tire3 a bal hátsó, és a tire4 a bal elsõnek. Nézzünk egy konkrét példát. Tegyük fel, hogy én a hátsó 2 abroncsot ki akarom durrantani, még az elsõ kettõt épre akarom beállítani. Ez esetben így néz ki a dolog: encode_tire(1,0,1,0); Ha kiíratnánk konzolba a visszatérési értéket (%d-vel), 5-öt kapnánk. Miért is? Az 5 bináris alakja 0101. Hoppá, ha megnézzük ez pont fordítottja annak, amit mi megadtunk számjegyekként, csak már konkrét bináris számként. Nézzük hogyan is történhetett mindez, mit is csináltunk. A függvény a következõ mûveletet végzi: tire1 | tire2 << 1 | tire3 << 2 | tire4 << 3 Konyhanyelven úgy magyarázhatnánk el a dolgot, hogy ez a kód egymás mellé pakolgatja a megadott számokat, fordított sorrendben. Na de mi most nem a konyhanyelv miatt vagyunk ebben a leírásban, szóval nézzük a kódunkat. Lényegében ez futott le, a konkrét értékeinkkel nézve: 1 | 0 << 1 | 1 << 2 | 0 << 3 Bármily furcsa is, ez egy mûvelet, melyen szépen végig halad sorban a program. Elõször is írjuk át az egészet bináris alakba(az egyszerûség kedvéért most csak 4 bitet fogok kiírni): 0001 | 0000 << 1 | 0001 << 2 | 0000 << 3 Na erre se mondtuk volna rá a tutorial elsõ részének elolvasása elõtt, hogy ez egy mûveletsor. A rendszer elsõnek az eltolásokat végzi el (olyan ez mint a 2*3+2*4+2*5 mûveletben elõször a szorzásokat végezzük el, és csak utána adunk össze, ha nincs zárójel), és csak utána a bitenkénti VAGY mûveleteket, ezért mi is így fogunk tenni. Emlékezzünk, eltoláskor az \"üres\" bitek 0 értéket fognak felvenni. Végezzük el külön csak az eltolásokat: 0000 << 1 = 00000001 << 2 = 01000000 << 3 0000 Nos, ha ezeket elvégeztük, akkor nézzük, hogy is áll most a kódunk: 0001 | 0000 | 0100 | 0000 Négy azonos mûvelet van hátra, így hát a program innen más szépen sorban fog haladni. Tegyünk mi is így. Elsõ tehát a 0001 | 0000 mûvelet elvégzése. Emlékeztetõül, a bitenkénti VAGY mûvelet akkor ad 0-t eredményül, ha mind a kettõ érték 0, minden más esetben 1-et. Végezzük el külön az elsõ bitenkénti VAGY mûveletet: 0001 | 0000 = 0001 Nem volt bonyolult, nézzük tehát hogy áll akkor most a teljes mûveletünk: 0001 | 0100 | 0000 Haladunk szépen tovább, elvégezzük a 0001 | 0100 mûveletet. 0001 | 0100 = 0101 Nincs mit magyarázni rajta, egyszerû bitenkénti VAGY mûvelet volt ez is. Már csak ez van hátra: 0101 | 0000 Nos, ez sem lesz egy bonyolult mûvelet: 0101 | 0000 = 0101 Ezzel meg is kaptuk a végeredményt, ami nem más, mint a 0101 (tízes számrendszerben 5). Mivel azonban eléggé kusza lett így a számításunk, nézzük egyben hogyan is néz ki az egész, ha nem bontom ennyire részekre: 0100 | 0000 << 1 | 0001 << 2 | 0000 << 3v0001 | 0000 | 0100 | 0000v0001 | 0100 | 0000v0101 | 0000v0101 Nos, ezennel láthattuk a gyakorlatban is, hogyan mûködik a függvény. Kijelenthetjük tehát, hogy a függvény a számokat a sorrendjüknek megfelelõ számú bitbe helyezi. Mivel azonban a biteket hátulról számozzuk, így az 1,0,1,0 bemenetek végeredménye 0101 lesz. Így már nem is olyan bonyolult Na, ha már ilyen sokáig eljutottunk, akkor felhasználhatjuk ezt a kódot arra, hogy az alapjait figyelembe véve elkészítsük a decode változatot. Mivel 4 értékkel térünk vissza, ezért paraméterként kell majd 4 változó, melyeknek módosíthatjuk az értékét, illetve 1 paraméter pedig a dekódolandó szám. A függvényünk \"címsora\" tehát így néz ki: decode_tires(tires, &tire1, &tire2, &tire3, &tire4) Hogy példánk ne csak elmélet legyen, a tires változó legyen az elõbbi 0101 értékkel egyenlõ. Na akkor menjünk tovább. Bemeneti értékünk ugye a \"tires\" változóban lesz eltárolva, így ezzel fogjuk a mûveleteket végezni. Kezdjünk el gondolkozni. Hogyan is tudnánk ellenõrizni egy adatot. Legegyszerûbb ha a bitenkénti ÉS mûveletet alkalmazzuk. Miért is? Nos, a bitenkénti ÉS mûvelet ugye akkor ad eredményül 1-et, ha mind a 2 érték egy. Tehát ha min egy számot összehasonlítunk a 1-el(binárisan ugye 00000000000000000000000000000001, akkor az eredményünk lényegében arról fog tájékoztatni, hogy az elsõ bit 0, vagy 1. Nem is olyan bonyolult ez. Nézzük akkor az elsõ bitet. Arra emlékezhetünk még, hogy az elsõ bit a tire1, vagyis jobb hátsó abroncs adatait tárolja. Tehát: tire1 = tires & 1; Vagy konkrétan a példánk esetében: tire1 = 0101 & 0001 Mivel a bitenkénti ÉS mûvelet akkor ad 1-et eredményül, ha mind a kettõ bit 1, akkor már láthatjuk is, hogy miért elegendõ az elsõ bitet szemügyre venni. Mivel mind a 2 helyen az elsõ bit 1, ezért a tire1 változónk értéke is 1(0001) lesz. Egyszerû és nagyszerû, máris megkaptuk a jobb hátsó abroncs állapotát.Gondolkozzunk hát tovább. Nekünk most arra van szükségünk, hogy a 2. bit értékét megkapjuk. Hogyan kaphatjuk ezt meg? Legegyszerûbb megoldás, ha a tires változót jobbra shifteljük 1-el, majd ismételten bitenkénti ÉS mûveletet végzünk. Nézzük tehát, hogyan is nézne ez ki: tire2= tires >> 1 & 1; Vagy konkrétan a mi példánkban: tire2 = 0101 >> 1 & 0001vtire2 = 0010 & 0001 Ismételten bitenkénti ÉS mûvelet, és az eredményünk 0, mivel csak az egyik bit 1, a másik 0, így a bitenkénti ÉS mûvelet eredménye is 0. Ezen az elven megcsináljuk a maradék 2 abronccsal is: tire3= tires >> 2 & 1;tire4= tires >> 3 & 1; Vagy a mi esetünkben konkrét példánkkal: tire3 = 0101 >> 2 & 0001 // azaz: --> 0001 & 0001 = 1tire4 = 0101 >> 3 & 0001 // azaz: --> 0000 & 0001 = 0 A teljes kódunk tehát: decode_tires(tires, &tire1, &tire2, &tire3, &tire4){ tire1 = tires & 1; tire2 = tires >> 1 & 1; tire3 = tires >> 2 & 1; tire4 = tires >> 3 & 1;} A konkrét példánkban a 4 változónk értéke a függvény lefutása után: tire1: 1tire2: 0tire3: 1tire4: 0 Ha összehasonlítjuk, ezek pont azok az értékek, mint amiket az encode függvényben megadtunk, tehát úgy tûnik jól dolgoztunk 2.3 encode_lightsEz a rész lesz a legrövidebb. Az egész lámpás rész pontosan ugyanarra a sémára épül mint az abroncsok. Az adott bitek ugyanazon a \"helyen\" lévõ lámpa adatait szolgáltatják, ugyanúgy 0 az ép, és 1 a sérült lámpák állapot azonosítója, és ugyanúgy 1-1 bit tárolja az értékeket.Maga a kód: encode_lights(light1, light2, light3, light4){ return light1 | light2 << 1 | light3 << 2 | light4 << 3;} Láthatjuk hogy halál pontosan ugyanaz mint az encode_tires, csak más változónevekkel. Így hát a decode függvény is ugyan az lesz, csak a változóneveket kell módosítanunk: decode_lights(lights, &light1, &light2, &light3, &light4){ light1 = lights & 1; light2 = lights >> 1 & 1; light3 = lights >> 2 & 1; light4 = lights >> 3 & 1;} Ha megnézzük látjuk, hogy teljesen ugyan az a séma, ugyan az a felépítés, minden megegyezik, ezért nem is írtam le újból ugyanazt, amit egyszer már kifejtettem.2.4 encode_panelsHáttér információ:A SA:MP 7 panelrõl tárol adatokat. Egy panel adatait 4 bit írja le. Nos, ennél több infóra nekünk most nincs szükségünk, az elõzõ kód alapján ezen kód is megérthetõ, így az encode verzióhoz nem is nagyon szeretnék magyarázatot fûzni. A különbség az encode_tires-hez képest, hogy itt 4-essével shiftelünk, mivel 1 panel adatait 4 bit tárolja.Maga a kód: encode_panels(flp, frp, rlp, rrp, windshield, front_bumper, rear_bumper){ return flp | frp << 4 | rlp << 8 | rrp << 12 | windshield << 16 | front_bumper << 20 | rear_bumper << 24;} Nézzünk egy decode verziót, ehhez a kódhoz. Alapjaiban ugyanazon a vonalon kell elindulnunk, mint a decode_tires esetében. Itt most 7 visszatérési értékünk lesz, és 1 bemeneti érték. Tehát így néz ki a függvény header: decode_panels(panels, &flp, &frp, &rlp, &rrp, &windshield, &front_bumper, &rear_bumper) A folytatásban megint csak az elõbbi verziót kell alapul vennünk, azonban itt lesz egy kis különbség. Egyértelmû, hogy mivel 1 panel adatait 4 bit tárolja, így a 4 bitet nekünk egyben kell lekérni, és egyben kell vele visszatérnünk. Megint csak a bitenkénti ÉS mûveletet fogjuk alkalmazni, azonban mivel 4 bitrõl van szó, így egy olyan számmal kell összehasonlítanunk, mely bináris alakban 1111. Ez a szám nem más, mint a 15. Hogy miért ez kell?Ha egy bitsorozat tartalmát ki szeretnénk másolni egy másik változóba, akkor a bitenkénti ÉS mûveletre vna szükségünk. A bitenkénti ÉS mûvelet akkor ad 1-et eredményül, ha mindkét bit 1, ha valamelyik 0, akkor a visszatérés már 0 lesz. Ergó tehát, ha egy csupa 1-esekbõl álló bitsorozattal vetjük össze az eredeti bitsorozatunkat, akkor lényegében lemásoljuk a sorozatot, egy másik változóba, hisz eredményül mindig az eredeti sozatot fogjuk visszakapni. Íme néhány példa: 0101 & 1111 = 01011111 & 1111 = 11110000 & 1111 = 00001011 & 1111 = 1011 Láthatjuk tehát, hogy ha csupa 1-est tartalmazó bitsorozattal hasonlítjuk össze az eredeti számunkat, akkor lényegében egy másolatot készítünk. Mivel nekünk erre a 4 bitre van szükségünk a 32-bõl, így pontosan ezt kell tennünk, ezen 4 bitet lemásolni egy másik változóba, hogy késõbb ezt majd egy másik függvénnyel még jobban darabokra szedhessük.Íme tehát akkor a kódunk elsõ sora: flp = panels & 15; Mivel 4-esével vizsgáljuk a biteket, így lényegében annyit kell még változtatnunk az elõzõ kódon(decode_tires), hogy az 1-esével növekvõ shiftelések helyett 4-esével shifteljünk. Azaz a 2. panel adatainak lekérdezése: frp = panels >> 4 & 15; Innen pedig már csak mindig további 4 shiftelést el kell végeznünk, és szépen végig haladhatunk a panels változó bitjein, és megkaphatjuk minden panel adatait külön-külön. rlp = panels >> 8 & 15;rrp = panels >> 12 & 15;windshield = panels >> 16 & 15;front_bumper= panels >> 20 & 15;rear_bumper = panels >> 24 & 15; Ezennel minden panel adatait megkaptuk. Ezután már akár a decode_tires függvényt felhasználva szét lehet szedni az adatokat darabokra, de akár egy saját névvel, és más változó nevekkel ellátott másik függvényt is létre lehet hozni a decode_tires mintájára.Tehát az egész kód egyben: decode_panels(panels, &flp, &frp, &rlp, &rrp, &windshield, &front_bumper, &rear_bumper){ flp = panels & 15; frp = panels >> 4 & 15; rlp = panels >> 8 & 15; rrp = panels >> 12 & 15; windshield = panels >> 16 & 15; front_bumper= panels >> 20 & 15; rear_bumper = panels >> 24 & 15;} Igazából a függvény ugyanarra a sémára épül mint a decode_tires, csak annyi a különbség, hogy 4 bitenként kapjuk az adatokat, nem pedig egyesével, mivel itt minden panelnek 4 adatát tudjuk majd még lekérdezni. Nem tudom lehet-e még ehhez a részhez hozzáfûzni valamit, szóval szerintem ugorjunk. 2.5 encode_doorsHáttér információ:A SA:MP képes minden jármû 4 \"ajtajának\" állapotát tárolni. Ezek az \"ajtók\"(Doors-t hogy fordíthatnám jobban?) konkrétan a motorháztetõ, csomagtartó, vezetõoldali ajtó, és anyósülés oldali ajtó. A 2 hátsó ajtó állapotát jelenleg még nincs lehetõségünk lekérdezni.Amikor lekérdezzük egy jármû \"ajtajának\" adatait, azt a SA:MP 4 bájtként(32 bit) adja vissza. Ebbõl a 4 bájtból 1 bájt(8 bit) jut minden egyes objektumra. Vagyis minden egyes objektum adatait 8 bit tárolja. Vagyis tárolhatná...A SA:MP csak az elsõ 3 bitet használja, a további bitek üresek lesznek, nincs funkciójuk, talán majd késõbb lesz. Az elsõ bit azt tárolja, hogy az objektum zárt[0], vagy nyitott[1](nem az jelenti, hogy bezárt, hanem becsukott/kinyitott) állapotban van-e. A második bit azt adja meg, hogy az objektum ép[0] vagy sérült[1]. A 3. bit arról tájékoztat, hogy az adott objektum rajta van-e még a jármûvön[0] vagy már leesett[1]. A többi bit mint mondtam üres. Ez majd a decode függvénynél lesz fontos.Nézzük tehát a konkrét kódot: encode_doors(bonnet, boot, driver_door, passenger_door, behind_driver_door, behind_passenger_door){ #pragma unused behind_driver_door #pragma unused behind_passenger_door return bonnet | boot << 8 | driver_door << 16 | passenger_door << 24;} A 2 hátsó ajtóval nem foglalkozunk, mivel azok jelenleg még nem támogatottak ugyebár, de a kód készítõje gondolt a jövõre is, vagy fogalmam sincs miért adta meg ezt a két paramétert is, hisz ígyis-úgyis módosítani kell a kódon... Ha behoznak még 2 ajtót, akkor az 1 bájtos tárolással már 6 bájt lenne, de 1 változó maximum 4 bájt lehet, tehát a kódot úgyis módosítani kell, ha meg arra gondoltak, hogy legalább a paramétereket nem kell majd szegény scriptereknek minden sorban átírni, az megint hülyeség, hisz ki adna értéket olyan paraméternek, amely nincs használva?...Nos mindegy, nem a kritizálásért vagyunk most itt. Ha megnézzük, a kód 8-as shiftelést használ, mivel egy objektum adatait 8 bit tárolja. A mûködési elv ugyanaz, mint az elõzõ encode függvényeknél, így erre nem kívánok megint kitérni.Nézzük azonban hogyan írnánk ehhez egy decode függvényt. Mikor megkérdeztem errõl ismerõsömet, õ azt mondta, ugyanúgy mint az elõzõ, decode_panels függvénynél, csak 8-as shiftelést használunk, és 15 helyett 255-el végezzük a bitenkénti ÉS mûveletet. 255-el? minek?No erre is ki fogok térni késõbb, de haladjunk a szokásos módon. 1 bemeneti paraméterünk van, és 4 kimeneti, vagyis a header rész a következõ: decode_doors(doors, &bonnet, &boot, &driver_door, &passenger_door) Ezzel meg is vagyunk, itt nem látok különösebb magyarázni valót. Én most nem fogok törõdni a 2 nem létezõ ajtóval, ha egy jövõbeni verzióban netán azokat is behoznák mint lekérdezhetõ objektum, akkor ígyis-úgyis módosítani kell majd a kódot, addig meg úgysincs rá szükségünk.Nos, mint az eddigi decode függvényeknél, elsõ feladatunk egy shiftelés mentes bitenkénti ÉS mûvelet elvégzése. Az egyik megoldás a következõ: bonnet = doors & 255; Lehet így is. Azonban véleményem szerint felesleges itt egy ekkora számmal \"szenvednünk\". Miért használjunk ekkora számot, és ezáltal bonyolítsuk feleslegesen a kódunkat, ha nekünk a 8 bitbõl csak az elsõ 3 lesz ami adatot tárol? Bõven elegendõ egy olyan szám, amely bináris alakja 111. Ez nem más, mint a 7. Tehát a következõ megoldás ugyanúgy helyes: bonnet = doors & 7; A két kód pontosan ugyanazt az eredményt fogja visszaadni, semmi különbség a kettõ között. Csupán elsõ esetében nagy számmal dolgozunk, még második verzió esetén csak akkorával, amekkora szükséges.A kód többi részében csak 8-as shifteléssel végigcammogunk a doors változó többi bitjén is: boot = doors >> 8 & 7;driver_door = doors >> 16 & 7;passenger_door = doors >> 24 & 7; A teljes kód egyben: decode_doors(doors, &bonnet, &boot, &driver_door, &passenger_door){ bonnet = doors & 7; boot = doors >> 8 & 7; driver_door = doors >> 16 & 7; passenger_door = doors >> 24 & 7;} A kód pont ugyanazon az elven épül fel, mint az összes többi decode függvényünk, csak épp a benne használatos értékek az \"ajtó\" objectumok adatstruktúrájához vannak igazítva.2.6 Egyéb ötletek gyakorlati alkalmazásra, hasznosításraJoggal felmerülhet a kérdés, hogy ennyi? Emiatt rágtuk végig magunkat ezen néhány tízezer karakteren? Nem. Nem ennyi. A SA:MP-ban rengeteg felhasználási módja lehet még a bitmûveleteknek. Mutatok is néhány dolgot, mire jók még.2.6.1 RGBA->HEX; HEX->RGBA konvertálásokNos, bármily \"hihetetlen\", erre is használható. Miért is? Nos, 16-os számrendszerben 0-9, valamint A-F \"szánjegyek\" léteznek. A hexadecimális számjegy decimális értékben 0-15 lehet. Ha megnézzük bináris értékeket, 0-15 az 4 bittel leírható, hiszen a 15 az bináris alakban 1111. De ha józan paraszti ésszel belegondolunk is. 16 az 24. Így tehát 1 hexadecimális érték, az 4 bináris értékkel írható le. Ebbõl már sejthetõ, hogy itt is shiftelésre lesz szükségünk. Naná Mivel 4 bit ír le egy hexadecimális számjegyet, így 4-esével kell majd shiftelnünk.Mi is az az RGBA?RGBA: A színek olyan meghatározási módja, melyben a 3 alapszín([r]ed, [g]reen, lue) mellett egy 4. érték, az úgynevezett [a]lpha is szerepel, amely az áttetszõséget adja meg. A legtöbb program(pl. paint, PS), és pl. a css(külön függvénnyel!) is elfogadja ezen értékeket decimális számokként(0-255) is, a SA:MP-ban azonban hexadecimális alakban kell megadnunk. Bármily furcsa a következõ 4 sor pont ugyan azt jelenti:\"Normál\" RGB alak(pl. paint, photoshop): Piros: 0, Zöld: 255, Kék: 255CSS alak(függvénnyel): rgba(0, 255, 255, 1);Pawn alak(1): 0x00FFFFFFPawn alak(2): 16777215 Ó igen, a Pawn-ban decimális számokként is megadhatjuk a színeket, hisz a program mindent bináris számrendszerbe fog átszámolni. De akár bináris alakban is megadhattuk volna:Pawn alak(3): 0b111111111111111111111111 Az, hogy mi a Pawn-nak hexadecimális alakban szoktuk megadni, annak az egyetlen oka, hogy így a leginkább \"átlátható\", és ez egy amúgy is elfogadott módja (mármint a hexadecimális számmal való megadás) a színek leírásának a számítástechnikában.Nos, mivel 1 szín 0 és 255 közötti értéket tartalmaz a piros, zöld és kék színekbõl, ez hexadecimálisan 2 számjeggyel leírható: 00-FF. Az FF értéke 255, aki nem hiszi, számoljon utána (162-1)2 hexadecimális számjegy az pedig 8 bináris számjeggyel leírható(00000000-11111111). Így már láthatjuk, hogy 8-as shiftelésre lesz szükségünk. Másra nincs is szükségünk, az RGBA->HEX konverter elkészítéséhez.stock RGBAToHex( r, g, b, a ){return a | b << 8 | g << 16 | r << 24;} Az, hogy miért fordított sorrendben kellett megadnunk az értékeket, az megint csak azzal magyarázható, hogy a biteket hétköznapi értelemben véve visszafelé számozzuk.Egy ellentét, azaz HexToRGBA függvény megírása sem túl bonyolult, ugyan ezt kell csinálnunk, csak visszafelé.stock HexToRGBA( colour, &r, &g, &b, &a ){a = colour & 0xFF;b = (colour >> 8 ) & 0xFF;g = (colour >> 16 ) & 0xFF;r = (colour >> 24 ) & 0xFF;} A 0xFF-et természetesen decimális értékkel is megadhatjuk(255).No akkor lehet ugye RGBA-t ARGB-be is konvertálni, az így néz ki:RGBAToARGBHEX(r,g,b,a){return (r & 0xff) << 24 | (g & 0xff) << 16 | (b & 0xff) << 8 | a & 0xff;} Ez máris egy újabb felhasználási módja a bitmûveleteknek. De, mutatok még 2.6.2 Boolen változók helyett IntegerAz al-al-alcímet elolvasva joggal felmerülhet a kérdés, hogy ez meg hogy jön ide. Nos ez úgy jön ide, hogy most egy kis memóriatakarékosságról fogok itt regélni. Adhattam volna azt is címnek, hogy miért ne használjunk boolean változókat, ha legalább 5 igaz/hamis értéket el akarunk tárolni.Nos akkor egy kis memórialecke. Azt már tisztáztuk, hogy az integer változók 32 bitesek, vagyis 4 bájtot foglalnak le a memóriában maguknak. A boolean típusú változókról azonban még nem volt szó. Mivel 1 értéket tárolnak csak, az is csak igaz vagy hamis lehet, így joggal gondolhatnánk, hogy 1 bit a mérete. Azonban nem...1 boolean változó 8 bitet, azaz 1 bájtot foglal le magának a memóriában. Hogy ez miért így van, az a számítógépek memóriahasználatával magyarázható. Manapság nincs mód arra, hogy a memóriában egyetlen bitet módosítsunk, csak 8-as \"csomagokban\", vagyis bájtonként tudjuk ezt megtenni. Hogy ez miért van így, azt most nem fogom itt fejtegetni Nos, ha megnézzük tehát, a boolean változó egy nagyon-nagyon pazarló megoldás. Hiszen 1 darab boolean változó 1 bájtot foglal, 4 darab boolean változó esetén ez már 4 bájt, ami megegyezik 1 integer változó méretével. Azonban, még a 4 boolean változóban 4 igaz/hamis érték kerülhet tárolásra, addig egy integerben 32 igaz/hamis értéket is eltárolhatunk.Bizony, 32 igaz/hamis érték. Hiszen 1 integer változó 32 bitbõl áll, ha minden bitet hasznosítunk, akkor a bitmûveletek felhasználásával 32 igaz/hamis értéket is eltárolhatunk. Összehasonlítva:32 darab boolean változó mérete 32 bájt.1 integer változó mérete: 4 bájt. Vagyis 32 boolean változó esetén nyolcad annyi memóriánkba kerül, ha egy darab integerben tároljuk el az értékeket. Persze 32 booleanra ritkán van szükség, de egy RP modban simán elõfordulhat. Gondoljunk csak bele... Van 32 boolean-tömbös változónk, ez a maximum slotszámot(1000) figyelembe véve: 32*1000 byte azaz 32000 byte ami ~31 kilobyte. Ha ezt egyetlen integer-tömbbel oldanánk meg: 4*1000 byte, azaz 4000 byte, ami ~3,9 kilobyte. Azért így nézve már nem annyira \"elhanyagolandó\" az a különbség szerintem =)Akár írhatnánk erre külön egy függvényt, mely az adott bit értékével tér vissza. Nem is bonyolult. Így már azok számára is használhatóak a bitmûveletek, akik nem értenek hozzá, és rengeteg memóriát takaríthatnak meg.Íme egy függvény az adatok kiolvasására:stock GetData(integer, bit){ return integer >> bit & 1; //vagy az 1 helyett akár 0x01 alakban is megadhatjuk, nincs jelentõsége} És a párja, adat írásra:stock SetData(&integer, bit, value){ integer |= value << bit; return 1;} Persze ezt még lehetne csinosítani, hogy pl. ne 0-tól hanem 1-tõl számoljuk a biteket, vagy hogy 31(vagy 32 ha 1-tõl számolunk) felett 0 legyen a return értéke, esetleg a value-re is rakhatnánk ellenõrzést, hogy 0-1-e az értéke, vagy más, de ilyenekkel most nem törõdtem.2.6.3 Több érték, egy integerbeNa ez az a rész, ami véleményem szerint már csak a nagyon megrögzött memóriafelszabadítók szerint nem lesz felesleges Amennyiben nincs szükségünk túl nagy számokra(pl. csak 0-15, vagy akár csak 0-7, de akár még a 0-255 is megfelel), akkor akár egy integerbe is tehetjük õket. Hogyan? Egyszerû. Csoportosítjuk a biteket. Tegyük fel, hogy maradván egy RP szervernél, el akarjuk tárolni ûbergigantikusan memóriakímélõ megoldással a játékos házának számát(ez legyen mondjuk 0-100 közötti szám), a játékos frakcióját(ez legyen 0-15 között), a játékos rangját(0-7 között), hogy a telefonja be van-e kapcsolva(0-1), a játékos szolgálati jármûvének id-jét(legyen 0-255 között), és az alapmunkáját(0-15 között). Ez is megoldható, egyetlen integerben, és ismételten 1000 slottal számolva máris megspóroltunk 4*4*1000+1*1000 byte-t(5 integer, és 1 boolean helyett csupán 1 integert használunk), ami ~16,6 kilobyte. Azért nem mindegy az, hogy 20,5 kb-ot vagy 4 kb-ot használunk szerintem... Ezt a következõ módszerrel valósíthatnánk meg:#define DATA_HOUSE 0#define DATA_FACTION 1#define DATA_RANK 2#define DATA_PHONESTATE 3#define DATA_DUTYCAR 4#define DATA_JOB 5stock GetData(playerinfo, data){ switch(data) { case DATA_HOUSE: return playerinfo & 127;//7 bit case DATA_FACTION: return playerinfo >> 7 & 15;//4 bit case DATA_RANK: return playerinfo >> 11 & 7;//3 bit case DATA_PHONESTATE: return playerinfo >> 14 & 1;//1 bit case DATA_DUTYCAR: return playerinfo >> 15 & 255;//8 bit case DATA_JOB: return playerinfo >> 23 & 15;//4 bit}return -1;}stock SetData(&playerinfo, data, value){ switch(data) { case DATA_HOUSE: { playerinfo = playerinfo & ~127 | value; }//7 bit (0-127 közötti maximálisan) case DATA_FACTION: { playerinfo = playerinfo & ~(15 << 7) | value << 7; }//4 bit (0-15 között) case DATA_RANK: { playerinfo = playerinfo & ~(7 << 11) | value << 11; }//3 bit (0-7 között) case DATA_PHONESTATE: { playerinfo = playerinfo & ~(1 << 14) | value << 14; }//1 bit (0-1) case DATA_DUTYCAR: { playerinfo = playerinfo & ~(255 << 15) | value << 15; }//8 bit (0-255 között) case DATA_JOB: { playerinfo = playerinfo & ~(15 << 23) | value << 23; }//4 bit (0-15 között) } return -1;}stock SetDatas(&playerinfo, house, faction, rank, phonestate, dutycar, job){playerinfo |= house | (faction << 7) | (rank << 11) | (phonestate << 14) | (dutycar << 15) | (job << 23);} Ez már azért elég morbid, ezt inkább csak érdekességképp mutattam, de éppenséggel ez is használható lenne, csak ehhez már kell egy kis õrültség 2.7 Második fejezet végeNos, láthattuk hogyan is mûködik mindazon sok \"marhaság\", amit az elsõ fejezetben elméletként átvettünk. Láthattuk hogyan mûködnek a jármûvek paneljének adataival babráló encode függvények, valamint megmutattam, hogyan írhatunk hozzájuk decode verziókat. Láthattunk még példát egyéb gyakorlati hasznosításra, kicsit megkoccoltuk a hexadecimális számok világát is, majd szóba került egy kis memóriatakarékosság. Végül mutattam egy összetettebb, elvetemült példát is, a bitmûveletek hasznosítására.3. Összegzés, végszó Úgy gondolom, hogy elég részletesen körbe jártuk a témát. Megnéztük a bitmûveletek elméleti, és gyakorlati oldalát is, láthattunk gyakorlati példákat. Nem kell elsõre megérteni ezt az egészet, sõt... Elegendõ ha egyszer elolvassuk, emésztjük kicsit, majd késõbb megint elõvesszük, akkor netán már kicsit többet is megértünk belõle. Nem egy kihagyhatatlan része a Pawno-nak, de azért van annak haszna, ha az ember jártas ebben a témában is, bár sokak szerint nem éri meg a sok fáradtságot, és idõt, mely a megértéséhez, begyakorlásához, használatának elsajátításához szükséges. Próbáltam mindent részletesen kifejteni. A bonyolultabb dolgokhoz sokat magyarázni, az egyszerûbbek esetén csak visszautalni az alapokhoz. Sok dolgot talán már túl is magyaráztam. Ezt a leírást nem 1 nap alatt készítettem el, kb. 1 hónapja írtam meg az elméleti oldalát, most az elmúlt pár napban pedig a gyakorlatot. Esténként 1-2 órát szántam rá csupán, és mivel ilyenkor már nem vagyok épp a toppon, elképzelhetõ, hogy néhány helyen helyesírási, vagy elvi hibát vétettem. Amennyiben valaki ilyet találna, kérem szóljon, és korrigálni fogom. Nos, azt hiszem nincs is más hátra, mint hogy sok szerencsét kívánjak a bitmûveletek elsajátításához, és megosszam a forrásokat. Remélem néhányan hasznosnak találják, eme igen hosszúra sikerült leírást.4. Források: Internet:WikipédiaSA:MP WikiSA:MP hivatalos fórumKönyv, e-book:Pohl László - A programozás alapjai(BME - Elektronikus Eszközök Tanszéke, Budapest, 2010)Siroki László - C programozás - Bitmûveletek jegyzeteˆ Anthony, 2012 - Minden jog fenntartva.A leírás egészben, vagy részleteiben szabadon terjeszthetõ, ameddig egyértelmûen fel van tüntetve az eredeti készítõ, valamint hogy honnan is származik. « Utoljára szerkesztve: 2012. augusztus 01. - 10:53:54 írta Anthony » Naplózva
00000000000000000000000000001011^0000000000000000000000000000011100000000000000000000000000001100[/quote]Eredményül most 00000000000000000000000000001100-t kaptunk, ami a kettes számrendszerben a 12-nek felel meg. Érdekes, hogy teljesen más eredményt kapunk, pedig lényegében ugyanazt az eredményt adja az operátor, - kis különbséggel ugyebár - mint a VAGY operátor.A következõ 2 sor tehát ugyanazt jelenti: Bin:00000000000000000000000000001011^00000000000000000000000000000111=00000000000000000000000000001100Dec:11^7=12[/quote]Megint néhány példa: 00000000000000000000000000000011 ^ 00000000000000000000000000000101 = 0000000000000000000000000000011000000000000000000000000001110110 ^ 00000000000000000000000001011001 = 00000000000000000000000000101111[/quote]Ezzel kapcsolatban egy érdekességet mutatnék meg. Vegyünk 2 számot. A látványosság kedvéért én példának a 118-at és a 89-et veszem. 1. szám: 00000000000000000000000001110110 = 1182. szám: 00000000000000000000000001011001 = 89Mûvelet elvégzése: 00000000000000000000000001110110 ^ 00000000000000000000000001011001 = 00000000000000000000000000101111 = 471. szám az eredmény, 2. szám az eredeti 2. szám: 00000000000000000000000001011001 = 89Eredmény: 00000000000000000000000000101111 ^ 00000000000000000000000001011001 = 00000000000000000000000001110110 = 118[/quote]Vagyis visszakaptuk a kiindulási állapotot 1.4.4 Bitenkénti (bitek közötti) NEGÁLÁSA bitenkénti NEGÁLÁS (NOT, ~) az utolsó logikai operátor, melyet meg fogok mutatni. Ez az operátor eléggé eltér az eddigiektõl, leginkább abban, hogy ezt a mûveletet nem 2 bitre alkalmazzuk, hanem csak 1-re. Szokás komplementer-operátornak is hívni(konyhanyelven). Lényegében az 1-esekbõl 0-t, a 0-kból 1-est csinál. Igazság táblázata: A ~A0110[/quote]Példának nézzük meg, mi történik, ha a bitenkénti NEGÁLÁST alkalmazzuk a 7-es számra. A 7 bináris számrendszerben mint már írtam 00000000000000000000000000000111-nek felel meg. Tehát a ~7, az ugyaz mint ha ezt írnám: ~00000000000000000000000000000111. Nézzük tehát, mi történik: ~0000000000000000000000000000011111111111111111111111111111111000[/quote]Eredményül most 11111111111111111111111111111000-t kaptunk, ami a kettes számrendszerben a -8-nak felel meg. A következõ 2 sor tehát ugyanazt jelenti: Bin:~00000000000000000000000000000111=11111111111111111111111111111000Dec:~7=-8[/quote]Megint néhány példa: ~00000000000000000000000000000101 = 11111111111111111111111111111011~00000000000000000000000001110110= 11111111111111111111111110001010[/quote]Ehhez kapcsolódóan lenne itt egy fontos megjegyzendõ dolog. Aki nem érti, hogy miért -8 a megoldás, az nem olvasta el elég rendesen a \"Részletesebben a bitekrõl a Pawn-ban\" részt.1.4.5 Biteltolás (arithmetic shift, shiftelés) operátor(ok)A shift operátorok(<<, >>) a bitek tologatására használhatóak. A << balra tol, a >> jobbra. Bal oldalt található a szám, amit tologatni kívánunk, jobb oldalt pedig az, hogy mennyivel szeretnénk eltolni.1.4.5.1 Jobbra toló operátor(>>):Ez az operátor a szám bitjeit jobbra tolja a megadott mértékkel. Egy bittel való eltolás, 2-vel való osztásnak felel meg. Nézzünk egy példát: 00000000000000000000000000001000//8>>200000000000000000000000000000010//2[/quote]Láthatjuk, hogy semmi más nem történt, mint minden bitet 2-vel hátrébb mozgattunk. Az így \"megüresedett\" bitek helyére pedig 0-k kerültek. Lényegében az történt, hogy az utolsó 2 bit kiesett, az így megmaradt biteket 2-vel hátrébb csúsztattuk, majd az elsõ 2, így \"üres\" bit 0 értéket kapott.Nézzünk egy példát negatív számmal: 11111111111111111111111111110000//-8>>211111111111111111111111111111100//-2[/quote]Láthatjuk, hogy itt fontos szerepe van az MSB-nek. Ettõl függ ugyanis, hogy 0-k vagy 1-esek kerülnek a \"megüresedett\" bitek helyére. Fontos tehát megjegyezni, hogy a jobbra shiftelés megtartja a számok elõjelét, nem változtat azon!1.4.5.2 Balra toló operátor(<<):Ez az operátor a szám bitjeit balra tolja a megadott mértékkel. Egy bittel való eltolás, 2-vel való szorzásnak felel meg. Nézzünk egy példát: 00000000000000000000000000001000//8<<200000000000000000000000000100000//32[/quote]Itt megint könnyedén észrevehetõ mi is történt. Minden bitet fentebb \"toltunk\" 2-vel. Azok a bitek, amelyek ezáltal a 32. bit elé kerültek, kiesnek. Az így megüresedett bitek 0-kkal egészültek ki. Eddig nem túl bonyolult. Azonban figyeljünk oda, ha túl sokáig shiftelünk egy pozitív számot, akkor az utolsó érték, melyet fel tud venni a változónk (mielõtt még túlcsordulás miatt elérné a 0-t), az egy negatív szám!Nézzük mi történik negatív számok esetén: 11111111111111111111111111111000//-8<<211111111111111111111111111100000//-32[/quote]Láthatjuk hogy a balra shifteléskor a szám mindig 0-kkal egészítõdik ki, legyen az akár negatív, akár pozitív. Nem veszi figyelembe az MSB értékét, így ha túl sokat shiftelünk, akkor elõfordulhat, hogy a szám elveszti negatív értékét, és eléri a 0-t.1.4.6 Logikai biteltolás (logical shift, logikai shiftelés) operátorElõször is le kell tisztázni, hogy logikai shiftelésbõl csak egy létezik, jobbra logikai shiftelés(>>>). Sokban hasonlít a jobbra shiftelõ operátorra (>>), azonban van egy fontos különbség.Nézzük az elsõ példát: 00000000000000000000000000001000//8>>>200000000000000000000000000000010//2[/quote]Eddig nem igazán tapasztalhattunk semmi változást, minden pont ugyanúgy zajlott, mint a jobbra shiftelésnél(>>).Nézzük mi történik negatív szám esetén: 11111111111111111111111111111000//-8>>>200111111111111111111111111111110 //1073741822[/quote]Itt már láthatjuk a különbséget. Logikai shiftelés esetén az üres bitek helyére bekerülõ érték nem függ az MSB-tõl, hanem mindig 0. Emiatt soha nem kaphatunk eredményül negatív számot, logikai shiftelés esetén.1.5 Elsõ fejezet végeNos, ezennel átvettünk mindent, amit érdemes tudni a bitmûveletekrõl. Megmutattam az alapok, hogy hogyan épülnek fel a változók, valamint átvettük a fõ operátorokat. Eddig csak színtiszta elmélet volt. A következõ részben megmutatom, hogyan hasznosíthatjuk mindezt a gyakorlatban, vagyis mire is jó az a sok marhaság, amit eddig leírtam.2. Gyakorlati példák, érdekességek 2.1 Áttekintés, mikrõl is lesz még szóAz alábbiakban még pár dolgot szeretnék mutatni. Elõször is néhány \"függvénnyel\" fogok foglalkozni, majd mutatok még egy-két érdekességet. Konkrétan megmutatom, hogyan tudjuk egy jármû minden \"paneljének\" adatát kiíratni, részletesen(erre alapból nincs lehetõség, és a függvények is mind \"encode\" függvények, én viszont mutatok egy-egy decode \"verziót\" is.)Az alábbi 4 függvénnyel fogok foglalkozni: encode_tiresencode_panelsencode_doorsencode_lightsDe elõbb egy kis háttérinfó:A Pawnban a forrásban megadhatunk bináris számokat ugyanúgy, ahogyan Hex számokat is megadhatunk. Ehhez a \"0b\" prefixet kell használnunk. Tehát ha bináris számunk az 1011 és ezt akarjuk adni értékül egy változónak, azt így tehetjük meg: new var = 0b1011; 2.2 encode_tiresHáttér információ:A SA:MP-ban egy jármû abroncsainak adataid 4 bit tárolja. Az elsõ bit a jobb hátsó, a második bit a jobb elsõ, a harmadik bit a bal hátsó, a negyedik bit pedig a bal elsõ abroncsról tárol információt. Mivel 4 bitben eltárolhatóak az információk, így decimális formában 0-15 értékekkel is be lehet állítani a jármûvek abroncsait. 16-tól kezdve az értékek már ismétlõdni kezdenek, szóval a 16-nál az eredmény ugyanaz mint 0-nál, 17-nél ugyanaz mind 1-nél, és így tovább. Ez érthetõ, ha tudjuk hogyan épül fel a kettes számrendszer.(Ne felejtsük el, a biteket hátulról számozzuk!)Nos nézzük hogyan néz ki a kód: encode_tires(tire1, tire2, tire3, tire4){ return tire1 | tire2 << 1 | tire3 << 2 | tire4 << 3;} Láthatjuk, hogy bekérjük a 4 abroncs adatát(vagyis hogy ép[0], vagy kidurrant[1]), majd egy sima bitenkénti VAGY mûvelet és biteltolás kombinálásával visszatérünk az eredménnyel. A kódban a tire1 felel meg a jobb hátsó abroncsnak, a tire2 a jobb elsõ, a tire3 a bal hátsó, és a tire4 a bal elsõnek. Nézzünk egy konkrét példát. Tegyük fel, hogy én a hátsó 2 abroncsot ki akarom durrantani, még az elsõ kettõt épre akarom beállítani. Ez esetben így néz ki a dolog: encode_tire(1,0,1,0); Ha kiíratnánk konzolba a visszatérési értéket (%d-vel), 5-öt kapnánk. Miért is? Az 5 bináris alakja 0101. Hoppá, ha megnézzük ez pont fordítottja annak, amit mi megadtunk számjegyekként, csak már konkrét bináris számként. Nézzük hogyan is történhetett mindez, mit is csináltunk. A függvény a következõ mûveletet végzi: tire1 | tire2 << 1 | tire3 << 2 | tire4 << 3 Konyhanyelven úgy magyarázhatnánk el a dolgot, hogy ez a kód egymás mellé pakolgatja a megadott számokat, fordított sorrendben. Na de mi most nem a konyhanyelv miatt vagyunk ebben a leírásban, szóval nézzük a kódunkat. Lényegében ez futott le, a konkrét értékeinkkel nézve: 1 | 0 << 1 | 1 << 2 | 0 << 3 Bármily furcsa is, ez egy mûvelet, melyen szépen végig halad sorban a program. Elõször is írjuk át az egészet bináris alakba(az egyszerûség kedvéért most csak 4 bitet fogok kiírni): 0001 | 0000 << 1 | 0001 << 2 | 0000 << 3 Na erre se mondtuk volna rá a tutorial elsõ részének elolvasása elõtt, hogy ez egy mûveletsor. A rendszer elsõnek az eltolásokat végzi el (olyan ez mint a 2*3+2*4+2*5 mûveletben elõször a szorzásokat végezzük el, és csak utána adunk össze, ha nincs zárójel), és csak utána a bitenkénti VAGY mûveleteket, ezért mi is így fogunk tenni. Emlékezzünk, eltoláskor az \"üres\" bitek 0 értéket fognak felvenni. Végezzük el külön csak az eltolásokat: 0000 << 1 = 00000001 << 2 = 01000000 << 3 0000 Nos, ha ezeket elvégeztük, akkor nézzük, hogy is áll most a kódunk: 0001 | 0000 | 0100 | 0000 Négy azonos mûvelet van hátra, így hát a program innen más szépen sorban fog haladni. Tegyünk mi is így. Elsõ tehát a 0001 | 0000 mûvelet elvégzése. Emlékeztetõül, a bitenkénti VAGY mûvelet akkor ad 0-t eredményül, ha mind a kettõ érték 0, minden más esetben 1-et. Végezzük el külön az elsõ bitenkénti VAGY mûveletet: 0001 | 0000 = 0001 Nem volt bonyolult, nézzük tehát hogy áll akkor most a teljes mûveletünk: 0001 | 0100 | 0000 Haladunk szépen tovább, elvégezzük a 0001 | 0100 mûveletet. 0001 | 0100 = 0101 Nincs mit magyarázni rajta, egyszerû bitenkénti VAGY mûvelet volt ez is. Már csak ez van hátra: 0101 | 0000 Nos, ez sem lesz egy bonyolult mûvelet: 0101 | 0000 = 0101 Ezzel meg is kaptuk a végeredményt, ami nem más, mint a 0101 (tízes számrendszerben 5). Mivel azonban eléggé kusza lett így a számításunk, nézzük egyben hogyan is néz ki az egész, ha nem bontom ennyire részekre: 0100 | 0000 << 1 | 0001 << 2 | 0000 << 3v0001 | 0000 | 0100 | 0000v0001 | 0100 | 0000v0101 | 0000v0101 Nos, ezennel láthattuk a gyakorlatban is, hogyan mûködik a függvény. Kijelenthetjük tehát, hogy a függvény a számokat a sorrendjüknek megfelelõ számú bitbe helyezi. Mivel azonban a biteket hátulról számozzuk, így az 1,0,1,0 bemenetek végeredménye 0101 lesz. Így már nem is olyan bonyolult Na, ha már ilyen sokáig eljutottunk, akkor felhasználhatjuk ezt a kódot arra, hogy az alapjait figyelembe véve elkészítsük a decode változatot. Mivel 4 értékkel térünk vissza, ezért paraméterként kell majd 4 változó, melyeknek módosíthatjuk az értékét, illetve 1 paraméter pedig a dekódolandó szám. A függvényünk \"címsora\" tehát így néz ki: decode_tires(tires, &tire1, &tire2, &tire3, &tire4) Hogy példánk ne csak elmélet legyen, a tires változó legyen az elõbbi 0101 értékkel egyenlõ. Na akkor menjünk tovább. Bemeneti értékünk ugye a \"tires\" változóban lesz eltárolva, így ezzel fogjuk a mûveleteket végezni. Kezdjünk el gondolkozni. Hogyan is tudnánk ellenõrizni egy adatot. Legegyszerûbb ha a bitenkénti ÉS mûveletet alkalmazzuk. Miért is? Nos, a bitenkénti ÉS mûvelet ugye akkor ad eredményül 1-et, ha mind a 2 érték egy. Tehát ha min egy számot összehasonlítunk a 1-el(binárisan ugye 00000000000000000000000000000001, akkor az eredményünk lényegében arról fog tájékoztatni, hogy az elsõ bit 0, vagy 1. Nem is olyan bonyolult ez. Nézzük akkor az elsõ bitet. Arra emlékezhetünk még, hogy az elsõ bit a tire1, vagyis jobb hátsó abroncs adatait tárolja. Tehát: tire1 = tires & 1; Vagy konkrétan a példánk esetében: tire1 = 0101 & 0001 Mivel a bitenkénti ÉS mûvelet akkor ad 1-et eredményül, ha mind a kettõ bit 1, akkor már láthatjuk is, hogy miért elegendõ az elsõ bitet szemügyre venni. Mivel mind a 2 helyen az elsõ bit 1, ezért a tire1 változónk értéke is 1(0001) lesz. Egyszerû és nagyszerû, máris megkaptuk a jobb hátsó abroncs állapotát.Gondolkozzunk hát tovább. Nekünk most arra van szükségünk, hogy a 2. bit értékét megkapjuk. Hogyan kaphatjuk ezt meg? Legegyszerûbb megoldás, ha a tires változót jobbra shifteljük 1-el, majd ismételten bitenkénti ÉS mûveletet végzünk. Nézzük tehát, hogyan is nézne ez ki: tire2= tires >> 1 & 1; Vagy konkrétan a mi példánkban: tire2 = 0101 >> 1 & 0001vtire2 = 0010 & 0001 Ismételten bitenkénti ÉS mûvelet, és az eredményünk 0, mivel csak az egyik bit 1, a másik 0, így a bitenkénti ÉS mûvelet eredménye is 0. Ezen az elven megcsináljuk a maradék 2 abronccsal is: tire3= tires >> 2 & 1;tire4= tires >> 3 & 1; Vagy a mi esetünkben konkrét példánkkal: tire3 = 0101 >> 2 & 0001 // azaz: --> 0001 & 0001 = 1tire4 = 0101 >> 3 & 0001 // azaz: --> 0000 & 0001 = 0 A teljes kódunk tehát: decode_tires(tires, &tire1, &tire2, &tire3, &tire4){ tire1 = tires & 1; tire2 = tires >> 1 & 1; tire3 = tires >> 2 & 1; tire4 = tires >> 3 & 1;} A konkrét példánkban a 4 változónk értéke a függvény lefutása után: tire1: 1tire2: 0tire3: 1tire4: 0 Ha összehasonlítjuk, ezek pont azok az értékek, mint amiket az encode függvényben megadtunk, tehát úgy tûnik jól dolgoztunk 2.3 encode_lightsEz a rész lesz a legrövidebb. Az egész lámpás rész pontosan ugyanarra a sémára épül mint az abroncsok. Az adott bitek ugyanazon a \"helyen\" lévõ lámpa adatait szolgáltatják, ugyanúgy 0 az ép, és 1 a sérült lámpák állapot azonosítója, és ugyanúgy 1-1 bit tárolja az értékeket.Maga a kód: encode_lights(light1, light2, light3, light4){ return light1 | light2 << 1 | light3 << 2 | light4 << 3;} Láthatjuk hogy halál pontosan ugyanaz mint az encode_tires, csak más változónevekkel. Így hát a decode függvény is ugyan az lesz, csak a változóneveket kell módosítanunk: decode_lights(lights, &light1, &light2, &light3, &light4){ light1 = lights & 1; light2 = lights >> 1 & 1; light3 = lights >> 2 & 1; light4 = lights >> 3 & 1;} Ha megnézzük látjuk, hogy teljesen ugyan az a séma, ugyan az a felépítés, minden megegyezik, ezért nem is írtam le újból ugyanazt, amit egyszer már kifejtettem.2.4 encode_panelsHáttér információ:A SA:MP 7 panelrõl tárol adatokat. Egy panel adatait 4 bit írja le. Nos, ennél több infóra nekünk most nincs szükségünk, az elõzõ kód alapján ezen kód is megérthetõ, így az encode verzióhoz nem is nagyon szeretnék magyarázatot fûzni. A különbség az encode_tires-hez képest, hogy itt 4-essével shiftelünk, mivel 1 panel adatait 4 bit tárolja.Maga a kód: encode_panels(flp, frp, rlp, rrp, windshield, front_bumper, rear_bumper){ return flp | frp << 4 | rlp << 8 | rrp << 12 | windshield << 16 | front_bumper << 20 | rear_bumper << 24;} Nézzünk egy decode verziót, ehhez a kódhoz. Alapjaiban ugyanazon a vonalon kell elindulnunk, mint a decode_tires esetében. Itt most 7 visszatérési értékünk lesz, és 1 bemeneti érték. Tehát így néz ki a függvény header: decode_panels(panels, &flp, &frp, &rlp, &rrp, &windshield, &front_bumper, &rear_bumper) A folytatásban megint csak az elõbbi verziót kell alapul vennünk, azonban itt lesz egy kis különbség. Egyértelmû, hogy mivel 1 panel adatait 4 bit tárolja, így a 4 bitet nekünk egyben kell lekérni, és egyben kell vele visszatérnünk. Megint csak a bitenkénti ÉS mûveletet fogjuk alkalmazni, azonban mivel 4 bitrõl van szó, így egy olyan számmal kell összehasonlítanunk, mely bináris alakban 1111. Ez a szám nem más, mint a 15. Hogy miért ez kell?Ha egy bitsorozat tartalmát ki szeretnénk másolni egy másik változóba, akkor a bitenkénti ÉS mûveletre vna szükségünk. A bitenkénti ÉS mûvelet akkor ad 1-et eredményül, ha mindkét bit 1, ha valamelyik 0, akkor a visszatérés már 0 lesz. Ergó tehát, ha egy csupa 1-esekbõl álló bitsorozattal vetjük össze az eredeti bitsorozatunkat, akkor lényegében lemásoljuk a sorozatot, egy másik változóba, hisz eredményül mindig az eredeti sozatot fogjuk visszakapni. Íme néhány példa: 0101 & 1111 = 01011111 & 1111 = 11110000 & 1111 = 00001011 & 1111 = 1011 Láthatjuk tehát, hogy ha csupa 1-est tartalmazó bitsorozattal hasonlítjuk össze az eredeti számunkat, akkor lényegében egy másolatot készítünk. Mivel nekünk erre a 4 bitre van szükségünk a 32-bõl, így pontosan ezt kell tennünk, ezen 4 bitet lemásolni egy másik változóba, hogy késõbb ezt majd egy másik függvénnyel még jobban darabokra szedhessük.Íme tehát akkor a kódunk elsõ sora: flp = panels & 15; Mivel 4-esével vizsgáljuk a biteket, így lényegében annyit kell még változtatnunk az elõzõ kódon(decode_tires), hogy az 1-esével növekvõ shiftelések helyett 4-esével shifteljünk. Azaz a 2. panel adatainak lekérdezése: frp = panels >> 4 & 15; Innen pedig már csak mindig további 4 shiftelést el kell végeznünk, és szépen végig haladhatunk a panels változó bitjein, és megkaphatjuk minden panel adatait külön-külön. rlp = panels >> 8 & 15;rrp = panels >> 12 & 15;windshield = panels >> 16 & 15;front_bumper= panels >> 20 & 15;rear_bumper = panels >> 24 & 15; Ezennel minden panel adatait megkaptuk. Ezután már akár a decode_tires függvényt felhasználva szét lehet szedni az adatokat darabokra, de akár egy saját névvel, és más változó nevekkel ellátott másik függvényt is létre lehet hozni a decode_tires mintájára.Tehát az egész kód egyben: decode_panels(panels, &flp, &frp, &rlp, &rrp, &windshield, &front_bumper, &rear_bumper){ flp = panels & 15; frp = panels >> 4 & 15; rlp = panels >> 8 & 15; rrp = panels >> 12 & 15; windshield = panels >> 16 & 15; front_bumper= panels >> 20 & 15; rear_bumper = panels >> 24 & 15;} Igazából a függvény ugyanarra a sémára épül mint a decode_tires, csak annyi a különbség, hogy 4 bitenként kapjuk az adatokat, nem pedig egyesével, mivel itt minden panelnek 4 adatát tudjuk majd még lekérdezni. Nem tudom lehet-e még ehhez a részhez hozzáfûzni valamit, szóval szerintem ugorjunk. 2.5 encode_doorsHáttér információ:A SA:MP képes minden jármû 4 \"ajtajának\" állapotát tárolni. Ezek az \"ajtók\"(Doors-t hogy fordíthatnám jobban?) konkrétan a motorháztetõ, csomagtartó, vezetõoldali ajtó, és anyósülés oldali ajtó. A 2 hátsó ajtó állapotát jelenleg még nincs lehetõségünk lekérdezni.Amikor lekérdezzük egy jármû \"ajtajának\" adatait, azt a SA:MP 4 bájtként(32 bit) adja vissza. Ebbõl a 4 bájtból 1 bájt(8 bit) jut minden egyes objektumra. Vagyis minden egyes objektum adatait 8 bit tárolja. Vagyis tárolhatná...A SA:MP csak az elsõ 3 bitet használja, a további bitek üresek lesznek, nincs funkciójuk, talán majd késõbb lesz. Az elsõ bit azt tárolja, hogy az objektum zárt[0], vagy nyitott[1](nem az jelenti, hogy bezárt, hanem becsukott/kinyitott) állapotban van-e. A második bit azt adja meg, hogy az objektum ép[0] vagy sérült[1]. A 3. bit arról tájékoztat, hogy az adott objektum rajta van-e még a jármûvön[0] vagy már leesett[1]. A többi bit mint mondtam üres. Ez majd a decode függvénynél lesz fontos.Nézzük tehát a konkrét kódot: encode_doors(bonnet, boot, driver_door, passenger_door, behind_driver_door, behind_passenger_door){ #pragma unused behind_driver_door #pragma unused behind_passenger_door return bonnet | boot << 8 | driver_door << 16 | passenger_door << 24;} A 2 hátsó ajtóval nem foglalkozunk, mivel azok jelenleg még nem támogatottak ugyebár, de a kód készítõje gondolt a jövõre is, vagy fogalmam sincs miért adta meg ezt a két paramétert is, hisz ígyis-úgyis módosítani kell a kódon... Ha behoznak még 2 ajtót, akkor az 1 bájtos tárolással már 6 bájt lenne, de 1 változó maximum 4 bájt lehet, tehát a kódot úgyis módosítani kell, ha meg arra gondoltak, hogy legalább a paramétereket nem kell majd szegény scriptereknek minden sorban átírni, az megint hülyeség, hisz ki adna értéket olyan paraméternek, amely nincs használva?...Nos mindegy, nem a kritizálásért vagyunk most itt. Ha megnézzük, a kód 8-as shiftelést használ, mivel egy objektum adatait 8 bit tárolja. A mûködési elv ugyanaz, mint az elõzõ encode függvényeknél, így erre nem kívánok megint kitérni.Nézzük azonban hogyan írnánk ehhez egy decode függvényt. Mikor megkérdeztem errõl ismerõsömet, õ azt mondta, ugyanúgy mint az elõzõ, decode_panels függvénynél, csak 8-as shiftelést használunk, és 15 helyett 255-el végezzük a bitenkénti ÉS mûveletet. 255-el? minek?No erre is ki fogok térni késõbb, de haladjunk a szokásos módon. 1 bemeneti paraméterünk van, és 4 kimeneti, vagyis a header rész a következõ: decode_doors(doors, &bonnet, &boot, &driver_door, &passenger_door) Ezzel meg is vagyunk, itt nem látok különösebb magyarázni valót. Én most nem fogok törõdni a 2 nem létezõ ajtóval, ha egy jövõbeni verzióban netán azokat is behoznák mint lekérdezhetõ objektum, akkor ígyis-úgyis módosítani kell majd a kódot, addig meg úgysincs rá szükségünk.Nos, mint az eddigi decode függvényeknél, elsõ feladatunk egy shiftelés mentes bitenkénti ÉS mûvelet elvégzése. Az egyik megoldás a következõ: bonnet = doors & 255; Lehet így is. Azonban véleményem szerint felesleges itt egy ekkora számmal \"szenvednünk\". Miért használjunk ekkora számot, és ezáltal bonyolítsuk feleslegesen a kódunkat, ha nekünk a 8 bitbõl csak az elsõ 3 lesz ami adatot tárol? Bõven elegendõ egy olyan szám, amely bináris alakja 111. Ez nem más, mint a 7. Tehát a következõ megoldás ugyanúgy helyes: bonnet = doors & 7; A két kód pontosan ugyanazt az eredményt fogja visszaadni, semmi különbség a kettõ között. Csupán elsõ esetében nagy számmal dolgozunk, még második verzió esetén csak akkorával, amekkora szükséges.A kód többi részében csak 8-as shifteléssel végigcammogunk a doors változó többi bitjén is: boot = doors >> 8 & 7;driver_door = doors >> 16 & 7;passenger_door = doors >> 24 & 7; A teljes kód egyben: decode_doors(doors, &bonnet, &boot, &driver_door, &passenger_door){ bonnet = doors & 7; boot = doors >> 8 & 7; driver_door = doors >> 16 & 7; passenger_door = doors >> 24 & 7;} A kód pont ugyanazon az elven épül fel, mint az összes többi decode függvényünk, csak épp a benne használatos értékek az \"ajtó\" objectumok adatstruktúrájához vannak igazítva.2.6 Egyéb ötletek gyakorlati alkalmazásra, hasznosításraJoggal felmerülhet a kérdés, hogy ennyi? Emiatt rágtuk végig magunkat ezen néhány tízezer karakteren? Nem. Nem ennyi. A SA:MP-ban rengeteg felhasználási módja lehet még a bitmûveleteknek. Mutatok is néhány dolgot, mire jók még.2.6.1 RGBA->HEX; HEX->RGBA konvertálásokNos, bármily \"hihetetlen\", erre is használható. Miért is? Nos, 16-os számrendszerben 0-9, valamint A-F \"szánjegyek\" léteznek. A hexadecimális számjegy decimális értékben 0-15 lehet. Ha megnézzük bináris értékeket, 0-15 az 4 bittel leírható, hiszen a 15 az bináris alakban 1111. De ha józan paraszti ésszel belegondolunk is. 16 az 24. Így tehát 1 hexadecimális érték, az 4 bináris értékkel írható le. Ebbõl már sejthetõ, hogy itt is shiftelésre lesz szükségünk. Naná Mivel 4 bit ír le egy hexadecimális számjegyet, így 4-esével kell majd shiftelnünk.Mi is az az RGBA?RGBA: A színek olyan meghatározási módja, melyben a 3 alapszín([r]ed, [g]reen, lue) mellett egy 4. érték, az úgynevezett [a]lpha is szerepel, amely az áttetszõséget adja meg. A legtöbb program(pl. paint, PS), és pl. a css(külön függvénnyel!) is elfogadja ezen értékeket decimális számokként(0-255) is, a SA:MP-ban azonban hexadecimális alakban kell megadnunk. Bármily furcsa a következõ 4 sor pont ugyan azt jelenti:\"Normál\" RGB alak(pl. paint, photoshop): Piros: 0, Zöld: 255, Kék: 255CSS alak(függvénnyel): rgba(0, 255, 255, 1);Pawn alak(1): 0x00FFFFFFPawn alak(2): 16777215 Ó igen, a Pawn-ban decimális számokként is megadhatjuk a színeket, hisz a program mindent bináris számrendszerbe fog átszámolni. De akár bináris alakban is megadhattuk volna:Pawn alak(3): 0b111111111111111111111111 Az, hogy mi a Pawn-nak hexadecimális alakban szoktuk megadni, annak az egyetlen oka, hogy így a leginkább \"átlátható\", és ez egy amúgy is elfogadott módja (mármint a hexadecimális számmal való megadás) a színek leírásának a számítástechnikában.Nos, mivel 1 szín 0 és 255 közötti értéket tartalmaz a piros, zöld és kék színekbõl, ez hexadecimálisan 2 számjeggyel leírható: 00-FF. Az FF értéke 255, aki nem hiszi, számoljon utána (162-1)2 hexadecimális számjegy az pedig 8 bináris számjeggyel leírható(00000000-11111111). Így már láthatjuk, hogy 8-as shiftelésre lesz szükségünk. Másra nincs is szükségünk, az RGBA->HEX konverter elkészítéséhez.stock RGBAToHex( r, g, b, a ){return a | b << 8 | g << 16 | r << 24;} Az, hogy miért fordított sorrendben kellett megadnunk az értékeket, az megint csak azzal magyarázható, hogy a biteket hétköznapi értelemben véve visszafelé számozzuk.Egy ellentét, azaz HexToRGBA függvény megírása sem túl bonyolult, ugyan ezt kell csinálnunk, csak visszafelé.stock HexToRGBA( colour, &r, &g, &b, &a ){a = colour & 0xFF;b = (colour >> 8 ) & 0xFF;g = (colour >> 16 ) & 0xFF;r = (colour >> 24 ) & 0xFF;} A 0xFF-et természetesen decimális értékkel is megadhatjuk(255).No akkor lehet ugye RGBA-t ARGB-be is konvertálni, az így néz ki:RGBAToARGBHEX(r,g,b,a){return (r & 0xff) << 24 | (g & 0xff) << 16 | (b & 0xff) << 8 | a & 0xff;} Ez máris egy újabb felhasználási módja a bitmûveleteknek. De, mutatok még 2.6.2 Boolen változók helyett IntegerAz al-al-alcímet elolvasva joggal felmerülhet a kérdés, hogy ez meg hogy jön ide. Nos ez úgy jön ide, hogy most egy kis memóriatakarékosságról fogok itt regélni. Adhattam volna azt is címnek, hogy miért ne használjunk boolean változókat, ha legalább 5 igaz/hamis értéket el akarunk tárolni.Nos akkor egy kis memórialecke. Azt már tisztáztuk, hogy az integer változók 32 bitesek, vagyis 4 bájtot foglalnak le a memóriában maguknak. A boolean típusú változókról azonban még nem volt szó. Mivel 1 értéket tárolnak csak, az is csak igaz vagy hamis lehet, így joggal gondolhatnánk, hogy 1 bit a mérete. Azonban nem...1 boolean változó 8 bitet, azaz 1 bájtot foglal le magának a memóriában. Hogy ez miért így van, az a számítógépek memóriahasználatával magyarázható. Manapság nincs mód arra, hogy a memóriában egyetlen bitet módosítsunk, csak 8-as \"csomagokban\", vagyis bájtonként tudjuk ezt megtenni. Hogy ez miért van így, azt most nem fogom itt fejtegetni Nos, ha megnézzük tehát, a boolean változó egy nagyon-nagyon pazarló megoldás. Hiszen 1 darab boolean változó 1 bájtot foglal, 4 darab boolean változó esetén ez már 4 bájt, ami megegyezik 1 integer változó méretével. Azonban, még a 4 boolean változóban 4 igaz/hamis érték kerülhet tárolásra, addig egy integerben 32 igaz/hamis értéket is eltárolhatunk.Bizony, 32 igaz/hamis érték. Hiszen 1 integer változó 32 bitbõl áll, ha minden bitet hasznosítunk, akkor a bitmûveletek felhasználásával 32 igaz/hamis értéket is eltárolhatunk. Összehasonlítva:32 darab boolean változó mérete 32 bájt.1 integer változó mérete: 4 bájt. Vagyis 32 boolean változó esetén nyolcad annyi memóriánkba kerül, ha egy darab integerben tároljuk el az értékeket. Persze 32 booleanra ritkán van szükség, de egy RP modban simán elõfordulhat. Gondoljunk csak bele... Van 32 boolean-tömbös változónk, ez a maximum slotszámot(1000) figyelembe véve: 32*1000 byte azaz 32000 byte ami ~31 kilobyte. Ha ezt egyetlen integer-tömbbel oldanánk meg: 4*1000 byte, azaz 4000 byte, ami ~3,9 kilobyte. Azért így nézve már nem annyira \"elhanyagolandó\" az a különbség szerintem =)Akár írhatnánk erre külön egy függvényt, mely az adott bit értékével tér vissza. Nem is bonyolult. Így már azok számára is használhatóak a bitmûveletek, akik nem értenek hozzá, és rengeteg memóriát takaríthatnak meg.Íme egy függvény az adatok kiolvasására:stock GetData(integer, bit){ return integer >> bit & 1; //vagy az 1 helyett akár 0x01 alakban is megadhatjuk, nincs jelentõsége} És a párja, adat írásra:stock SetData(&integer, bit, value){ integer |= value << bit; return 1;} Persze ezt még lehetne csinosítani, hogy pl. ne 0-tól hanem 1-tõl számoljuk a biteket, vagy hogy 31(vagy 32 ha 1-tõl számolunk) felett 0 legyen a return értéke, esetleg a value-re is rakhatnánk ellenõrzést, hogy 0-1-e az értéke, vagy más, de ilyenekkel most nem törõdtem.2.6.3 Több érték, egy integerbeNa ez az a rész, ami véleményem szerint már csak a nagyon megrögzött memóriafelszabadítók szerint nem lesz felesleges Amennyiben nincs szükségünk túl nagy számokra(pl. csak 0-15, vagy akár csak 0-7, de akár még a 0-255 is megfelel), akkor akár egy integerbe is tehetjük õket. Hogyan? Egyszerû. Csoportosítjuk a biteket. Tegyük fel, hogy maradván egy RP szervernél, el akarjuk tárolni ûbergigantikusan memóriakímélõ megoldással a játékos házának számát(ez legyen mondjuk 0-100 közötti szám), a játékos frakcióját(ez legyen 0-15 között), a játékos rangját(0-7 között), hogy a telefonja be van-e kapcsolva(0-1), a játékos szolgálati jármûvének id-jét(legyen 0-255 között), és az alapmunkáját(0-15 között). Ez is megoldható, egyetlen integerben, és ismételten 1000 slottal számolva máris megspóroltunk 4*4*1000+1*1000 byte-t(5 integer, és 1 boolean helyett csupán 1 integert használunk), ami ~16,6 kilobyte. Azért nem mindegy az, hogy 20,5 kb-ot vagy 4 kb-ot használunk szerintem... Ezt a következõ módszerrel valósíthatnánk meg:#define DATA_HOUSE 0#define DATA_FACTION 1#define DATA_RANK 2#define DATA_PHONESTATE 3#define DATA_DUTYCAR 4#define DATA_JOB 5stock GetData(playerinfo, data){ switch(data) { case DATA_HOUSE: return playerinfo & 127;//7 bit case DATA_FACTION: return playerinfo >> 7 & 15;//4 bit case DATA_RANK: return playerinfo >> 11 & 7;//3 bit case DATA_PHONESTATE: return playerinfo >> 14 & 1;//1 bit case DATA_DUTYCAR: return playerinfo >> 15 & 255;//8 bit case DATA_JOB: return playerinfo >> 23 & 15;//4 bit}return -1;}stock SetData(&playerinfo, data, value){ switch(data) { case DATA_HOUSE: { playerinfo = playerinfo & ~127 | value; }//7 bit (0-127 közötti maximálisan) case DATA_FACTION: { playerinfo = playerinfo & ~(15 << 7) | value << 7; }//4 bit (0-15 között) case DATA_RANK: { playerinfo = playerinfo & ~(7 << 11) | value << 11; }//3 bit (0-7 között) case DATA_PHONESTATE: { playerinfo = playerinfo & ~(1 << 14) | value << 14; }//1 bit (0-1) case DATA_DUTYCAR: { playerinfo = playerinfo & ~(255 << 15) | value << 15; }//8 bit (0-255 között) case DATA_JOB: { playerinfo = playerinfo & ~(15 << 23) | value << 23; }//4 bit (0-15 között) } return -1;}stock SetDatas(&playerinfo, house, faction, rank, phonestate, dutycar, job){playerinfo |= house | (faction << 7) | (rank << 11) | (phonestate << 14) | (dutycar << 15) | (job << 23);} Ez már azért elég morbid, ezt inkább csak érdekességképp mutattam, de éppenséggel ez is használható lenne, csak ehhez már kell egy kis õrültség 2.7 Második fejezet végeNos, láthattuk hogyan is mûködik mindazon sok \"marhaság\", amit az elsõ fejezetben elméletként átvettünk. Láthattuk hogyan mûködnek a jármûvek paneljének adataival babráló encode függvények, valamint megmutattam, hogyan írhatunk hozzájuk decode verziókat. Láthattunk még példát egyéb gyakorlati hasznosításra, kicsit megkoccoltuk a hexadecimális számok világát is, majd szóba került egy kis memóriatakarékosság. Végül mutattam egy összetettebb, elvetemült példát is, a bitmûveletek hasznosítására.3. Összegzés, végszó Úgy gondolom, hogy elég részletesen körbe jártuk a témát. Megnéztük a bitmûveletek elméleti, és gyakorlati oldalát is, láthattunk gyakorlati példákat. Nem kell elsõre megérteni ezt az egészet, sõt... Elegendõ ha egyszer elolvassuk, emésztjük kicsit, majd késõbb megint elõvesszük, akkor netán már kicsit többet is megértünk belõle. Nem egy kihagyhatatlan része a Pawno-nak, de azért van annak haszna, ha az ember jártas ebben a témában is, bár sokak szerint nem éri meg a sok fáradtságot, és idõt, mely a megértéséhez, begyakorlásához, használatának elsajátításához szükséges. Próbáltam mindent részletesen kifejteni. A bonyolultabb dolgokhoz sokat magyarázni, az egyszerûbbek esetén csak visszautalni az alapokhoz. Sok dolgot talán már túl is magyaráztam. Ezt a leírást nem 1 nap alatt készítettem el, kb. 1 hónapja írtam meg az elméleti oldalát, most az elmúlt pár napban pedig a gyakorlatot. Esténként 1-2 órát szántam rá csupán, és mivel ilyenkor már nem vagyok épp a toppon, elképzelhetõ, hogy néhány helyen helyesírási, vagy elvi hibát vétettem. Amennyiben valaki ilyet találna, kérem szóljon, és korrigálni fogom. Nos, azt hiszem nincs is más hátra, mint hogy sok szerencsét kívánjak a bitmûveletek elsajátításához, és megosszam a forrásokat. Remélem néhányan hasznosnak találják, eme igen hosszúra sikerült leírást.4. Források: Internet:WikipédiaSA:MP WikiSA:MP hivatalos fórumKönyv, e-book:Pohl László - A programozás alapjai(BME - Elektronikus Eszközök Tanszéke, Budapest, 2010)Siroki László - C programozás - Bitmûveletek jegyzeteˆ Anthony, 2012 - Minden jog fenntartva.A leírás egészben, vagy részleteiben szabadon terjeszthetõ, ameddig egyértelmûen fel van tüntetve az eredeti készítõ, valamint hogy honnan is származik. « Utoljára szerkesztve: 2012. augusztus 01. - 10:53:54 írta Anthony » Naplózva
Bin:00000000000000000000000000001011^00000000000000000000000000000111=00000000000000000000000000001100Dec:11^7=12[/quote]Megint néhány példa: 00000000000000000000000000000011 ^ 00000000000000000000000000000101 = 0000000000000000000000000000011000000000000000000000000001110110 ^ 00000000000000000000000001011001 = 00000000000000000000000000101111[/quote]Ezzel kapcsolatban egy érdekességet mutatnék meg. Vegyünk 2 számot. A látványosság kedvéért én példának a 118-at és a 89-et veszem. 1. szám: 00000000000000000000000001110110 = 1182. szám: 00000000000000000000000001011001 = 89Mûvelet elvégzése: 00000000000000000000000001110110 ^ 00000000000000000000000001011001 = 00000000000000000000000000101111 = 471. szám az eredmény, 2. szám az eredeti 2. szám: 00000000000000000000000001011001 = 89Eredmény: 00000000000000000000000000101111 ^ 00000000000000000000000001011001 = 00000000000000000000000001110110 = 118[/quote]Vagyis visszakaptuk a kiindulási állapotot 1.4.4 Bitenkénti (bitek közötti) NEGÁLÁSA bitenkénti NEGÁLÁS (NOT, ~) az utolsó logikai operátor, melyet meg fogok mutatni. Ez az operátor eléggé eltér az eddigiektõl, leginkább abban, hogy ezt a mûveletet nem 2 bitre alkalmazzuk, hanem csak 1-re. Szokás komplementer-operátornak is hívni(konyhanyelven). Lényegében az 1-esekbõl 0-t, a 0-kból 1-est csinál. Igazság táblázata: A ~A0110[/quote]Példának nézzük meg, mi történik, ha a bitenkénti NEGÁLÁST alkalmazzuk a 7-es számra. A 7 bináris számrendszerben mint már írtam 00000000000000000000000000000111-nek felel meg. Tehát a ~7, az ugyaz mint ha ezt írnám: ~00000000000000000000000000000111. Nézzük tehát, mi történik: ~0000000000000000000000000000011111111111111111111111111111111000[/quote]Eredményül most 11111111111111111111111111111000-t kaptunk, ami a kettes számrendszerben a -8-nak felel meg. A következõ 2 sor tehát ugyanazt jelenti: Bin:~00000000000000000000000000000111=11111111111111111111111111111000Dec:~7=-8[/quote]Megint néhány példa: ~00000000000000000000000000000101 = 11111111111111111111111111111011~00000000000000000000000001110110= 11111111111111111111111110001010[/quote]Ehhez kapcsolódóan lenne itt egy fontos megjegyzendõ dolog. Aki nem érti, hogy miért -8 a megoldás, az nem olvasta el elég rendesen a \"Részletesebben a bitekrõl a Pawn-ban\" részt.1.4.5 Biteltolás (arithmetic shift, shiftelés) operátor(ok)A shift operátorok(<<, >>) a bitek tologatására használhatóak. A << balra tol, a >> jobbra. Bal oldalt található a szám, amit tologatni kívánunk, jobb oldalt pedig az, hogy mennyivel szeretnénk eltolni.1.4.5.1 Jobbra toló operátor(>>):Ez az operátor a szám bitjeit jobbra tolja a megadott mértékkel. Egy bittel való eltolás, 2-vel való osztásnak felel meg. Nézzünk egy példát: 00000000000000000000000000001000//8>>200000000000000000000000000000010//2[/quote]Láthatjuk, hogy semmi más nem történt, mint minden bitet 2-vel hátrébb mozgattunk. Az így \"megüresedett\" bitek helyére pedig 0-k kerültek. Lényegében az történt, hogy az utolsó 2 bit kiesett, az így megmaradt biteket 2-vel hátrébb csúsztattuk, majd az elsõ 2, így \"üres\" bit 0 értéket kapott.Nézzünk egy példát negatív számmal: 11111111111111111111111111110000//-8>>211111111111111111111111111111100//-2[/quote]Láthatjuk, hogy itt fontos szerepe van az MSB-nek. Ettõl függ ugyanis, hogy 0-k vagy 1-esek kerülnek a \"megüresedett\" bitek helyére. Fontos tehát megjegyezni, hogy a jobbra shiftelés megtartja a számok elõjelét, nem változtat azon!1.4.5.2 Balra toló operátor(<<):Ez az operátor a szám bitjeit balra tolja a megadott mértékkel. Egy bittel való eltolás, 2-vel való szorzásnak felel meg. Nézzünk egy példát: 00000000000000000000000000001000//8<<200000000000000000000000000100000//32[/quote]Itt megint könnyedén észrevehetõ mi is történt. Minden bitet fentebb \"toltunk\" 2-vel. Azok a bitek, amelyek ezáltal a 32. bit elé kerültek, kiesnek. Az így megüresedett bitek 0-kkal egészültek ki. Eddig nem túl bonyolult. Azonban figyeljünk oda, ha túl sokáig shiftelünk egy pozitív számot, akkor az utolsó érték, melyet fel tud venni a változónk (mielõtt még túlcsordulás miatt elérné a 0-t), az egy negatív szám!Nézzük mi történik negatív számok esetén: 11111111111111111111111111111000//-8<<211111111111111111111111111100000//-32[/quote]Láthatjuk hogy a balra shifteléskor a szám mindig 0-kkal egészítõdik ki, legyen az akár negatív, akár pozitív. Nem veszi figyelembe az MSB értékét, így ha túl sokat shiftelünk, akkor elõfordulhat, hogy a szám elveszti negatív értékét, és eléri a 0-t.1.4.6 Logikai biteltolás (logical shift, logikai shiftelés) operátorElõször is le kell tisztázni, hogy logikai shiftelésbõl csak egy létezik, jobbra logikai shiftelés(>>>). Sokban hasonlít a jobbra shiftelõ operátorra (>>), azonban van egy fontos különbség.Nézzük az elsõ példát: 00000000000000000000000000001000//8>>>200000000000000000000000000000010//2[/quote]Eddig nem igazán tapasztalhattunk semmi változást, minden pont ugyanúgy zajlott, mint a jobbra shiftelésnél(>>).Nézzük mi történik negatív szám esetén: 11111111111111111111111111111000//-8>>>200111111111111111111111111111110 //1073741822[/quote]Itt már láthatjuk a különbséget. Logikai shiftelés esetén az üres bitek helyére bekerülõ érték nem függ az MSB-tõl, hanem mindig 0. Emiatt soha nem kaphatunk eredményül negatív számot, logikai shiftelés esetén.1.5 Elsõ fejezet végeNos, ezennel átvettünk mindent, amit érdemes tudni a bitmûveletekrõl. Megmutattam az alapok, hogy hogyan épülnek fel a változók, valamint átvettük a fõ operátorokat. Eddig csak színtiszta elmélet volt. A következõ részben megmutatom, hogyan hasznosíthatjuk mindezt a gyakorlatban, vagyis mire is jó az a sok marhaság, amit eddig leírtam.2. Gyakorlati példák, érdekességek 2.1 Áttekintés, mikrõl is lesz még szóAz alábbiakban még pár dolgot szeretnék mutatni. Elõször is néhány \"függvénnyel\" fogok foglalkozni, majd mutatok még egy-két érdekességet. Konkrétan megmutatom, hogyan tudjuk egy jármû minden \"paneljének\" adatát kiíratni, részletesen(erre alapból nincs lehetõség, és a függvények is mind \"encode\" függvények, én viszont mutatok egy-egy decode \"verziót\" is.)Az alábbi 4 függvénnyel fogok foglalkozni: encode_tiresencode_panelsencode_doorsencode_lightsDe elõbb egy kis háttérinfó:A Pawnban a forrásban megadhatunk bináris számokat ugyanúgy, ahogyan Hex számokat is megadhatunk. Ehhez a \"0b\" prefixet kell használnunk. Tehát ha bináris számunk az 1011 és ezt akarjuk adni értékül egy változónak, azt így tehetjük meg: new var = 0b1011; 2.2 encode_tiresHáttér információ:A SA:MP-ban egy jármû abroncsainak adataid 4 bit tárolja. Az elsõ bit a jobb hátsó, a második bit a jobb elsõ, a harmadik bit a bal hátsó, a negyedik bit pedig a bal elsõ abroncsról tárol információt. Mivel 4 bitben eltárolhatóak az információk, így decimális formában 0-15 értékekkel is be lehet állítani a jármûvek abroncsait. 16-tól kezdve az értékek már ismétlõdni kezdenek, szóval a 16-nál az eredmény ugyanaz mint 0-nál, 17-nél ugyanaz mind 1-nél, és így tovább. Ez érthetõ, ha tudjuk hogyan épül fel a kettes számrendszer.(Ne felejtsük el, a biteket hátulról számozzuk!)Nos nézzük hogyan néz ki a kód: encode_tires(tire1, tire2, tire3, tire4){ return tire1 | tire2 << 1 | tire3 << 2 | tire4 << 3;} Láthatjuk, hogy bekérjük a 4 abroncs adatát(vagyis hogy ép[0], vagy kidurrant[1]), majd egy sima bitenkénti VAGY mûvelet és biteltolás kombinálásával visszatérünk az eredménnyel. A kódban a tire1 felel meg a jobb hátsó abroncsnak, a tire2 a jobb elsõ, a tire3 a bal hátsó, és a tire4 a bal elsõnek. Nézzünk egy konkrét példát. Tegyük fel, hogy én a hátsó 2 abroncsot ki akarom durrantani, még az elsõ kettõt épre akarom beállítani. Ez esetben így néz ki a dolog: encode_tire(1,0,1,0); Ha kiíratnánk konzolba a visszatérési értéket (%d-vel), 5-öt kapnánk. Miért is? Az 5 bináris alakja 0101. Hoppá, ha megnézzük ez pont fordítottja annak, amit mi megadtunk számjegyekként, csak már konkrét bináris számként. Nézzük hogyan is történhetett mindez, mit is csináltunk. A függvény a következõ mûveletet végzi: tire1 | tire2 << 1 | tire3 << 2 | tire4 << 3 Konyhanyelven úgy magyarázhatnánk el a dolgot, hogy ez a kód egymás mellé pakolgatja a megadott számokat, fordított sorrendben. Na de mi most nem a konyhanyelv miatt vagyunk ebben a leírásban, szóval nézzük a kódunkat. Lényegében ez futott le, a konkrét értékeinkkel nézve: 1 | 0 << 1 | 1 << 2 | 0 << 3 Bármily furcsa is, ez egy mûvelet, melyen szépen végig halad sorban a program. Elõször is írjuk át az egészet bináris alakba(az egyszerûség kedvéért most csak 4 bitet fogok kiírni): 0001 | 0000 << 1 | 0001 << 2 | 0000 << 3 Na erre se mondtuk volna rá a tutorial elsõ részének elolvasása elõtt, hogy ez egy mûveletsor. A rendszer elsõnek az eltolásokat végzi el (olyan ez mint a 2*3+2*4+2*5 mûveletben elõször a szorzásokat végezzük el, és csak utána adunk össze, ha nincs zárójel), és csak utána a bitenkénti VAGY mûveleteket, ezért mi is így fogunk tenni. Emlékezzünk, eltoláskor az \"üres\" bitek 0 értéket fognak felvenni. Végezzük el külön csak az eltolásokat: 0000 << 1 = 00000001 << 2 = 01000000 << 3 0000 Nos, ha ezeket elvégeztük, akkor nézzük, hogy is áll most a kódunk: 0001 | 0000 | 0100 | 0000 Négy azonos mûvelet van hátra, így hát a program innen más szépen sorban fog haladni. Tegyünk mi is így. Elsõ tehát a 0001 | 0000 mûvelet elvégzése. Emlékeztetõül, a bitenkénti VAGY mûvelet akkor ad 0-t eredményül, ha mind a kettõ érték 0, minden más esetben 1-et. Végezzük el külön az elsõ bitenkénti VAGY mûveletet: 0001 | 0000 = 0001 Nem volt bonyolult, nézzük tehát hogy áll akkor most a teljes mûveletünk: 0001 | 0100 | 0000 Haladunk szépen tovább, elvégezzük a 0001 | 0100 mûveletet. 0001 | 0100 = 0101 Nincs mit magyarázni rajta, egyszerû bitenkénti VAGY mûvelet volt ez is. Már csak ez van hátra: 0101 | 0000 Nos, ez sem lesz egy bonyolult mûvelet: 0101 | 0000 = 0101 Ezzel meg is kaptuk a végeredményt, ami nem más, mint a 0101 (tízes számrendszerben 5). Mivel azonban eléggé kusza lett így a számításunk, nézzük egyben hogyan is néz ki az egész, ha nem bontom ennyire részekre: 0100 | 0000 << 1 | 0001 << 2 | 0000 << 3v0001 | 0000 | 0100 | 0000v0001 | 0100 | 0000v0101 | 0000v0101 Nos, ezennel láthattuk a gyakorlatban is, hogyan mûködik a függvény. Kijelenthetjük tehát, hogy a függvény a számokat a sorrendjüknek megfelelõ számú bitbe helyezi. Mivel azonban a biteket hátulról számozzuk, így az 1,0,1,0 bemenetek végeredménye 0101 lesz. Így már nem is olyan bonyolult Na, ha már ilyen sokáig eljutottunk, akkor felhasználhatjuk ezt a kódot arra, hogy az alapjait figyelembe véve elkészítsük a decode változatot. Mivel 4 értékkel térünk vissza, ezért paraméterként kell majd 4 változó, melyeknek módosíthatjuk az értékét, illetve 1 paraméter pedig a dekódolandó szám. A függvényünk \"címsora\" tehát így néz ki: decode_tires(tires, &tire1, &tire2, &tire3, &tire4) Hogy példánk ne csak elmélet legyen, a tires változó legyen az elõbbi 0101 értékkel egyenlõ. Na akkor menjünk tovább. Bemeneti értékünk ugye a \"tires\" változóban lesz eltárolva, így ezzel fogjuk a mûveleteket végezni. Kezdjünk el gondolkozni. Hogyan is tudnánk ellenõrizni egy adatot. Legegyszerûbb ha a bitenkénti ÉS mûveletet alkalmazzuk. Miért is? Nos, a bitenkénti ÉS mûvelet ugye akkor ad eredményül 1-et, ha mind a 2 érték egy. Tehát ha min egy számot összehasonlítunk a 1-el(binárisan ugye 00000000000000000000000000000001, akkor az eredményünk lényegében arról fog tájékoztatni, hogy az elsõ bit 0, vagy 1. Nem is olyan bonyolult ez. Nézzük akkor az elsõ bitet. Arra emlékezhetünk még, hogy az elsõ bit a tire1, vagyis jobb hátsó abroncs adatait tárolja. Tehát: tire1 = tires & 1; Vagy konkrétan a példánk esetében: tire1 = 0101 & 0001 Mivel a bitenkénti ÉS mûvelet akkor ad 1-et eredményül, ha mind a kettõ bit 1, akkor már láthatjuk is, hogy miért elegendõ az elsõ bitet szemügyre venni. Mivel mind a 2 helyen az elsõ bit 1, ezért a tire1 változónk értéke is 1(0001) lesz. Egyszerû és nagyszerû, máris megkaptuk a jobb hátsó abroncs állapotát.Gondolkozzunk hát tovább. Nekünk most arra van szükségünk, hogy a 2. bit értékét megkapjuk. Hogyan kaphatjuk ezt meg? Legegyszerûbb megoldás, ha a tires változót jobbra shifteljük 1-el, majd ismételten bitenkénti ÉS mûveletet végzünk. Nézzük tehát, hogyan is nézne ez ki: tire2= tires >> 1 & 1; Vagy konkrétan a mi példánkban: tire2 = 0101 >> 1 & 0001vtire2 = 0010 & 0001 Ismételten bitenkénti ÉS mûvelet, és az eredményünk 0, mivel csak az egyik bit 1, a másik 0, így a bitenkénti ÉS mûvelet eredménye is 0. Ezen az elven megcsináljuk a maradék 2 abronccsal is: tire3= tires >> 2 & 1;tire4= tires >> 3 & 1; Vagy a mi esetünkben konkrét példánkkal: tire3 = 0101 >> 2 & 0001 // azaz: --> 0001 & 0001 = 1tire4 = 0101 >> 3 & 0001 // azaz: --> 0000 & 0001 = 0 A teljes kódunk tehát: decode_tires(tires, &tire1, &tire2, &tire3, &tire4){ tire1 = tires & 1; tire2 = tires >> 1 & 1; tire3 = tires >> 2 & 1; tire4 = tires >> 3 & 1;} A konkrét példánkban a 4 változónk értéke a függvény lefutása után: tire1: 1tire2: 0tire3: 1tire4: 0 Ha összehasonlítjuk, ezek pont azok az értékek, mint amiket az encode függvényben megadtunk, tehát úgy tûnik jól dolgoztunk 2.3 encode_lightsEz a rész lesz a legrövidebb. Az egész lámpás rész pontosan ugyanarra a sémára épül mint az abroncsok. Az adott bitek ugyanazon a \"helyen\" lévõ lámpa adatait szolgáltatják, ugyanúgy 0 az ép, és 1 a sérült lámpák állapot azonosítója, és ugyanúgy 1-1 bit tárolja az értékeket.Maga a kód: encode_lights(light1, light2, light3, light4){ return light1 | light2 << 1 | light3 << 2 | light4 << 3;} Láthatjuk hogy halál pontosan ugyanaz mint az encode_tires, csak más változónevekkel. Így hát a decode függvény is ugyan az lesz, csak a változóneveket kell módosítanunk: decode_lights(lights, &light1, &light2, &light3, &light4){ light1 = lights & 1; light2 = lights >> 1 & 1; light3 = lights >> 2 & 1; light4 = lights >> 3 & 1;} Ha megnézzük látjuk, hogy teljesen ugyan az a séma, ugyan az a felépítés, minden megegyezik, ezért nem is írtam le újból ugyanazt, amit egyszer már kifejtettem.2.4 encode_panelsHáttér információ:A SA:MP 7 panelrõl tárol adatokat. Egy panel adatait 4 bit írja le. Nos, ennél több infóra nekünk most nincs szükségünk, az elõzõ kód alapján ezen kód is megérthetõ, így az encode verzióhoz nem is nagyon szeretnék magyarázatot fûzni. A különbség az encode_tires-hez képest, hogy itt 4-essével shiftelünk, mivel 1 panel adatait 4 bit tárolja.Maga a kód: encode_panels(flp, frp, rlp, rrp, windshield, front_bumper, rear_bumper){ return flp | frp << 4 | rlp << 8 | rrp << 12 | windshield << 16 | front_bumper << 20 | rear_bumper << 24;} Nézzünk egy decode verziót, ehhez a kódhoz. Alapjaiban ugyanazon a vonalon kell elindulnunk, mint a decode_tires esetében. Itt most 7 visszatérési értékünk lesz, és 1 bemeneti érték. Tehát így néz ki a függvény header: decode_panels(panels, &flp, &frp, &rlp, &rrp, &windshield, &front_bumper, &rear_bumper) A folytatásban megint csak az elõbbi verziót kell alapul vennünk, azonban itt lesz egy kis különbség. Egyértelmû, hogy mivel 1 panel adatait 4 bit tárolja, így a 4 bitet nekünk egyben kell lekérni, és egyben kell vele visszatérnünk. Megint csak a bitenkénti ÉS mûveletet fogjuk alkalmazni, azonban mivel 4 bitrõl van szó, így egy olyan számmal kell összehasonlítanunk, mely bináris alakban 1111. Ez a szám nem más, mint a 15. Hogy miért ez kell?Ha egy bitsorozat tartalmát ki szeretnénk másolni egy másik változóba, akkor a bitenkénti ÉS mûveletre vna szükségünk. A bitenkénti ÉS mûvelet akkor ad 1-et eredményül, ha mindkét bit 1, ha valamelyik 0, akkor a visszatérés már 0 lesz. Ergó tehát, ha egy csupa 1-esekbõl álló bitsorozattal vetjük össze az eredeti bitsorozatunkat, akkor lényegében lemásoljuk a sorozatot, egy másik változóba, hisz eredményül mindig az eredeti sozatot fogjuk visszakapni. Íme néhány példa: 0101 & 1111 = 01011111 & 1111 = 11110000 & 1111 = 00001011 & 1111 = 1011 Láthatjuk tehát, hogy ha csupa 1-est tartalmazó bitsorozattal hasonlítjuk össze az eredeti számunkat, akkor lényegében egy másolatot készítünk. Mivel nekünk erre a 4 bitre van szükségünk a 32-bõl, így pontosan ezt kell tennünk, ezen 4 bitet lemásolni egy másik változóba, hogy késõbb ezt majd egy másik függvénnyel még jobban darabokra szedhessük.Íme tehát akkor a kódunk elsõ sora: flp = panels & 15; Mivel 4-esével vizsgáljuk a biteket, így lényegében annyit kell még változtatnunk az elõzõ kódon(decode_tires), hogy az 1-esével növekvõ shiftelések helyett 4-esével shifteljünk. Azaz a 2. panel adatainak lekérdezése: frp = panels >> 4 & 15; Innen pedig már csak mindig további 4 shiftelést el kell végeznünk, és szépen végig haladhatunk a panels változó bitjein, és megkaphatjuk minden panel adatait külön-külön. rlp = panels >> 8 & 15;rrp = panels >> 12 & 15;windshield = panels >> 16 & 15;front_bumper= panels >> 20 & 15;rear_bumper = panels >> 24 & 15; Ezennel minden panel adatait megkaptuk. Ezután már akár a decode_tires függvényt felhasználva szét lehet szedni az adatokat darabokra, de akár egy saját névvel, és más változó nevekkel ellátott másik függvényt is létre lehet hozni a decode_tires mintájára.Tehát az egész kód egyben: decode_panels(panels, &flp, &frp, &rlp, &rrp, &windshield, &front_bumper, &rear_bumper){ flp = panels & 15; frp = panels >> 4 & 15; rlp = panels >> 8 & 15; rrp = panels >> 12 & 15; windshield = panels >> 16 & 15; front_bumper= panels >> 20 & 15; rear_bumper = panels >> 24 & 15;} Igazából a függvény ugyanarra a sémára épül mint a decode_tires, csak annyi a különbség, hogy 4 bitenként kapjuk az adatokat, nem pedig egyesével, mivel itt minden panelnek 4 adatát tudjuk majd még lekérdezni. Nem tudom lehet-e még ehhez a részhez hozzáfûzni valamit, szóval szerintem ugorjunk. 2.5 encode_doorsHáttér információ:A SA:MP képes minden jármû 4 \"ajtajának\" állapotát tárolni. Ezek az \"ajtók\"(Doors-t hogy fordíthatnám jobban?) konkrétan a motorháztetõ, csomagtartó, vezetõoldali ajtó, és anyósülés oldali ajtó. A 2 hátsó ajtó állapotát jelenleg még nincs lehetõségünk lekérdezni.Amikor lekérdezzük egy jármû \"ajtajának\" adatait, azt a SA:MP 4 bájtként(32 bit) adja vissza. Ebbõl a 4 bájtból 1 bájt(8 bit) jut minden egyes objektumra. Vagyis minden egyes objektum adatait 8 bit tárolja. Vagyis tárolhatná...A SA:MP csak az elsõ 3 bitet használja, a további bitek üresek lesznek, nincs funkciójuk, talán majd késõbb lesz. Az elsõ bit azt tárolja, hogy az objektum zárt[0], vagy nyitott[1](nem az jelenti, hogy bezárt, hanem becsukott/kinyitott) állapotban van-e. A második bit azt adja meg, hogy az objektum ép[0] vagy sérült[1]. A 3. bit arról tájékoztat, hogy az adott objektum rajta van-e még a jármûvön[0] vagy már leesett[1]. A többi bit mint mondtam üres. Ez majd a decode függvénynél lesz fontos.Nézzük tehát a konkrét kódot: encode_doors(bonnet, boot, driver_door, passenger_door, behind_driver_door, behind_passenger_door){ #pragma unused behind_driver_door #pragma unused behind_passenger_door return bonnet | boot << 8 | driver_door << 16 | passenger_door << 24;} A 2 hátsó ajtóval nem foglalkozunk, mivel azok jelenleg még nem támogatottak ugyebár, de a kód készítõje gondolt a jövõre is, vagy fogalmam sincs miért adta meg ezt a két paramétert is, hisz ígyis-úgyis módosítani kell a kódon... Ha behoznak még 2 ajtót, akkor az 1 bájtos tárolással már 6 bájt lenne, de 1 változó maximum 4 bájt lehet, tehát a kódot úgyis módosítani kell, ha meg arra gondoltak, hogy legalább a paramétereket nem kell majd szegény scriptereknek minden sorban átírni, az megint hülyeség, hisz ki adna értéket olyan paraméternek, amely nincs használva?...Nos mindegy, nem a kritizálásért vagyunk most itt. Ha megnézzük, a kód 8-as shiftelést használ, mivel egy objektum adatait 8 bit tárolja. A mûködési elv ugyanaz, mint az elõzõ encode függvényeknél, így erre nem kívánok megint kitérni.Nézzük azonban hogyan írnánk ehhez egy decode függvényt. Mikor megkérdeztem errõl ismerõsömet, õ azt mondta, ugyanúgy mint az elõzõ, decode_panels függvénynél, csak 8-as shiftelést használunk, és 15 helyett 255-el végezzük a bitenkénti ÉS mûveletet. 255-el? minek?No erre is ki fogok térni késõbb, de haladjunk a szokásos módon. 1 bemeneti paraméterünk van, és 4 kimeneti, vagyis a header rész a következõ: decode_doors(doors, &bonnet, &boot, &driver_door, &passenger_door) Ezzel meg is vagyunk, itt nem látok különösebb magyarázni valót. Én most nem fogok törõdni a 2 nem létezõ ajtóval, ha egy jövõbeni verzióban netán azokat is behoznák mint lekérdezhetõ objektum, akkor ígyis-úgyis módosítani kell majd a kódot, addig meg úgysincs rá szükségünk.Nos, mint az eddigi decode függvényeknél, elsõ feladatunk egy shiftelés mentes bitenkénti ÉS mûvelet elvégzése. Az egyik megoldás a következõ: bonnet = doors & 255; Lehet így is. Azonban véleményem szerint felesleges itt egy ekkora számmal \"szenvednünk\". Miért használjunk ekkora számot, és ezáltal bonyolítsuk feleslegesen a kódunkat, ha nekünk a 8 bitbõl csak az elsõ 3 lesz ami adatot tárol? Bõven elegendõ egy olyan szám, amely bináris alakja 111. Ez nem más, mint a 7. Tehát a következõ megoldás ugyanúgy helyes: bonnet = doors & 7; A két kód pontosan ugyanazt az eredményt fogja visszaadni, semmi különbség a kettõ között. Csupán elsõ esetében nagy számmal dolgozunk, még második verzió esetén csak akkorával, amekkora szükséges.A kód többi részében csak 8-as shifteléssel végigcammogunk a doors változó többi bitjén is: boot = doors >> 8 & 7;driver_door = doors >> 16 & 7;passenger_door = doors >> 24 & 7; A teljes kód egyben: decode_doors(doors, &bonnet, &boot, &driver_door, &passenger_door){ bonnet = doors & 7; boot = doors >> 8 & 7; driver_door = doors >> 16 & 7; passenger_door = doors >> 24 & 7;} A kód pont ugyanazon az elven épül fel, mint az összes többi decode függvényünk, csak épp a benne használatos értékek az \"ajtó\" objectumok adatstruktúrájához vannak igazítva.2.6 Egyéb ötletek gyakorlati alkalmazásra, hasznosításraJoggal felmerülhet a kérdés, hogy ennyi? Emiatt rágtuk végig magunkat ezen néhány tízezer karakteren? Nem. Nem ennyi. A SA:MP-ban rengeteg felhasználási módja lehet még a bitmûveleteknek. Mutatok is néhány dolgot, mire jók még.2.6.1 RGBA->HEX; HEX->RGBA konvertálásokNos, bármily \"hihetetlen\", erre is használható. Miért is? Nos, 16-os számrendszerben 0-9, valamint A-F \"szánjegyek\" léteznek. A hexadecimális számjegy decimális értékben 0-15 lehet. Ha megnézzük bináris értékeket, 0-15 az 4 bittel leírható, hiszen a 15 az bináris alakban 1111. De ha józan paraszti ésszel belegondolunk is. 16 az 24. Így tehát 1 hexadecimális érték, az 4 bináris értékkel írható le. Ebbõl már sejthetõ, hogy itt is shiftelésre lesz szükségünk. Naná Mivel 4 bit ír le egy hexadecimális számjegyet, így 4-esével kell majd shiftelnünk.Mi is az az RGBA?RGBA: A színek olyan meghatározási módja, melyben a 3 alapszín([r]ed, [g]reen, lue) mellett egy 4. érték, az úgynevezett [a]lpha is szerepel, amely az áttetszõséget adja meg. A legtöbb program(pl. paint, PS), és pl. a css(külön függvénnyel!) is elfogadja ezen értékeket decimális számokként(0-255) is, a SA:MP-ban azonban hexadecimális alakban kell megadnunk. Bármily furcsa a következõ 4 sor pont ugyan azt jelenti:\"Normál\" RGB alak(pl. paint, photoshop): Piros: 0, Zöld: 255, Kék: 255CSS alak(függvénnyel): rgba(0, 255, 255, 1);Pawn alak(1): 0x00FFFFFFPawn alak(2): 16777215 Ó igen, a Pawn-ban decimális számokként is megadhatjuk a színeket, hisz a program mindent bináris számrendszerbe fog átszámolni. De akár bináris alakban is megadhattuk volna:Pawn alak(3): 0b111111111111111111111111 Az, hogy mi a Pawn-nak hexadecimális alakban szoktuk megadni, annak az egyetlen oka, hogy így a leginkább \"átlátható\", és ez egy amúgy is elfogadott módja (mármint a hexadecimális számmal való megadás) a színek leírásának a számítástechnikában.Nos, mivel 1 szín 0 és 255 közötti értéket tartalmaz a piros, zöld és kék színekbõl, ez hexadecimálisan 2 számjeggyel leírható: 00-FF. Az FF értéke 255, aki nem hiszi, számoljon utána (162-1)2 hexadecimális számjegy az pedig 8 bináris számjeggyel leírható(00000000-11111111). Így már láthatjuk, hogy 8-as shiftelésre lesz szükségünk. Másra nincs is szükségünk, az RGBA->HEX konverter elkészítéséhez.stock RGBAToHex( r, g, b, a ){return a | b << 8 | g << 16 | r << 24;} Az, hogy miért fordított sorrendben kellett megadnunk az értékeket, az megint csak azzal magyarázható, hogy a biteket hétköznapi értelemben véve visszafelé számozzuk.Egy ellentét, azaz HexToRGBA függvény megírása sem túl bonyolult, ugyan ezt kell csinálnunk, csak visszafelé.stock HexToRGBA( colour, &r, &g, &b, &a ){a = colour & 0xFF;b = (colour >> 8 ) & 0xFF;g = (colour >> 16 ) & 0xFF;r = (colour >> 24 ) & 0xFF;} A 0xFF-et természetesen decimális értékkel is megadhatjuk(255).No akkor lehet ugye RGBA-t ARGB-be is konvertálni, az így néz ki:RGBAToARGBHEX(r,g,b,a){return (r & 0xff) << 24 | (g & 0xff) << 16 | (b & 0xff) << 8 | a & 0xff;} Ez máris egy újabb felhasználási módja a bitmûveleteknek. De, mutatok még 2.6.2 Boolen változók helyett IntegerAz al-al-alcímet elolvasva joggal felmerülhet a kérdés, hogy ez meg hogy jön ide. Nos ez úgy jön ide, hogy most egy kis memóriatakarékosságról fogok itt regélni. Adhattam volna azt is címnek, hogy miért ne használjunk boolean változókat, ha legalább 5 igaz/hamis értéket el akarunk tárolni.Nos akkor egy kis memórialecke. Azt már tisztáztuk, hogy az integer változók 32 bitesek, vagyis 4 bájtot foglalnak le a memóriában maguknak. A boolean típusú változókról azonban még nem volt szó. Mivel 1 értéket tárolnak csak, az is csak igaz vagy hamis lehet, így joggal gondolhatnánk, hogy 1 bit a mérete. Azonban nem...1 boolean változó 8 bitet, azaz 1 bájtot foglal le magának a memóriában. Hogy ez miért így van, az a számítógépek memóriahasználatával magyarázható. Manapság nincs mód arra, hogy a memóriában egyetlen bitet módosítsunk, csak 8-as \"csomagokban\", vagyis bájtonként tudjuk ezt megtenni. Hogy ez miért van így, azt most nem fogom itt fejtegetni Nos, ha megnézzük tehát, a boolean változó egy nagyon-nagyon pazarló megoldás. Hiszen 1 darab boolean változó 1 bájtot foglal, 4 darab boolean változó esetén ez már 4 bájt, ami megegyezik 1 integer változó méretével. Azonban, még a 4 boolean változóban 4 igaz/hamis érték kerülhet tárolásra, addig egy integerben 32 igaz/hamis értéket is eltárolhatunk.Bizony, 32 igaz/hamis érték. Hiszen 1 integer változó 32 bitbõl áll, ha minden bitet hasznosítunk, akkor a bitmûveletek felhasználásával 32 igaz/hamis értéket is eltárolhatunk. Összehasonlítva:32 darab boolean változó mérete 32 bájt.1 integer változó mérete: 4 bájt. Vagyis 32 boolean változó esetén nyolcad annyi memóriánkba kerül, ha egy darab integerben tároljuk el az értékeket. Persze 32 booleanra ritkán van szükség, de egy RP modban simán elõfordulhat. Gondoljunk csak bele... Van 32 boolean-tömbös változónk, ez a maximum slotszámot(1000) figyelembe véve: 32*1000 byte azaz 32000 byte ami ~31 kilobyte. Ha ezt egyetlen integer-tömbbel oldanánk meg: 4*1000 byte, azaz 4000 byte, ami ~3,9 kilobyte. Azért így nézve már nem annyira \"elhanyagolandó\" az a különbség szerintem =)Akár írhatnánk erre külön egy függvényt, mely az adott bit értékével tér vissza. Nem is bonyolult. Így már azok számára is használhatóak a bitmûveletek, akik nem értenek hozzá, és rengeteg memóriát takaríthatnak meg.Íme egy függvény az adatok kiolvasására:stock GetData(integer, bit){ return integer >> bit & 1; //vagy az 1 helyett akár 0x01 alakban is megadhatjuk, nincs jelentõsége} És a párja, adat írásra:stock SetData(&integer, bit, value){ integer |= value << bit; return 1;} Persze ezt még lehetne csinosítani, hogy pl. ne 0-tól hanem 1-tõl számoljuk a biteket, vagy hogy 31(vagy 32 ha 1-tõl számolunk) felett 0 legyen a return értéke, esetleg a value-re is rakhatnánk ellenõrzést, hogy 0-1-e az értéke, vagy más, de ilyenekkel most nem törõdtem.2.6.3 Több érték, egy integerbeNa ez az a rész, ami véleményem szerint már csak a nagyon megrögzött memóriafelszabadítók szerint nem lesz felesleges Amennyiben nincs szükségünk túl nagy számokra(pl. csak 0-15, vagy akár csak 0-7, de akár még a 0-255 is megfelel), akkor akár egy integerbe is tehetjük õket. Hogyan? Egyszerû. Csoportosítjuk a biteket. Tegyük fel, hogy maradván egy RP szervernél, el akarjuk tárolni ûbergigantikusan memóriakímélõ megoldással a játékos házának számát(ez legyen mondjuk 0-100 közötti szám), a játékos frakcióját(ez legyen 0-15 között), a játékos rangját(0-7 között), hogy a telefonja be van-e kapcsolva(0-1), a játékos szolgálati jármûvének id-jét(legyen 0-255 között), és az alapmunkáját(0-15 között). Ez is megoldható, egyetlen integerben, és ismételten 1000 slottal számolva máris megspóroltunk 4*4*1000+1*1000 byte-t(5 integer, és 1 boolean helyett csupán 1 integert használunk), ami ~16,6 kilobyte. Azért nem mindegy az, hogy 20,5 kb-ot vagy 4 kb-ot használunk szerintem... Ezt a következõ módszerrel valósíthatnánk meg:#define DATA_HOUSE 0#define DATA_FACTION 1#define DATA_RANK 2#define DATA_PHONESTATE 3#define DATA_DUTYCAR 4#define DATA_JOB 5stock GetData(playerinfo, data){ switch(data) { case DATA_HOUSE: return playerinfo & 127;//7 bit case DATA_FACTION: return playerinfo >> 7 & 15;//4 bit case DATA_RANK: return playerinfo >> 11 & 7;//3 bit case DATA_PHONESTATE: return playerinfo >> 14 & 1;//1 bit case DATA_DUTYCAR: return playerinfo >> 15 & 255;//8 bit case DATA_JOB: return playerinfo >> 23 & 15;//4 bit}return -1;}stock SetData(&playerinfo, data, value){ switch(data) { case DATA_HOUSE: { playerinfo = playerinfo & ~127 | value; }//7 bit (0-127 közötti maximálisan) case DATA_FACTION: { playerinfo = playerinfo & ~(15 << 7) | value << 7; }//4 bit (0-15 között) case DATA_RANK: { playerinfo = playerinfo & ~(7 << 11) | value << 11; }//3 bit (0-7 között) case DATA_PHONESTATE: { playerinfo = playerinfo & ~(1 << 14) | value << 14; }//1 bit (0-1) case DATA_DUTYCAR: { playerinfo = playerinfo & ~(255 << 15) | value << 15; }//8 bit (0-255 között) case DATA_JOB: { playerinfo = playerinfo & ~(15 << 23) | value << 23; }//4 bit (0-15 között) } return -1;}stock SetDatas(&playerinfo, house, faction, rank, phonestate, dutycar, job){playerinfo |= house | (faction << 7) | (rank << 11) | (phonestate << 14) | (dutycar << 15) | (job << 23);} Ez már azért elég morbid, ezt inkább csak érdekességképp mutattam, de éppenséggel ez is használható lenne, csak ehhez már kell egy kis õrültség 2.7 Második fejezet végeNos, láthattuk hogyan is mûködik mindazon sok \"marhaság\", amit az elsõ fejezetben elméletként átvettünk. Láthattuk hogyan mûködnek a jármûvek paneljének adataival babráló encode függvények, valamint megmutattam, hogyan írhatunk hozzájuk decode verziókat. Láthattunk még példát egyéb gyakorlati hasznosításra, kicsit megkoccoltuk a hexadecimális számok világát is, majd szóba került egy kis memóriatakarékosság. Végül mutattam egy összetettebb, elvetemült példát is, a bitmûveletek hasznosítására.3. Összegzés, végszó Úgy gondolom, hogy elég részletesen körbe jártuk a témát. Megnéztük a bitmûveletek elméleti, és gyakorlati oldalát is, láthattunk gyakorlati példákat. Nem kell elsõre megérteni ezt az egészet, sõt... Elegendõ ha egyszer elolvassuk, emésztjük kicsit, majd késõbb megint elõvesszük, akkor netán már kicsit többet is megértünk belõle. Nem egy kihagyhatatlan része a Pawno-nak, de azért van annak haszna, ha az ember jártas ebben a témában is, bár sokak szerint nem éri meg a sok fáradtságot, és idõt, mely a megértéséhez, begyakorlásához, használatának elsajátításához szükséges. Próbáltam mindent részletesen kifejteni. A bonyolultabb dolgokhoz sokat magyarázni, az egyszerûbbek esetén csak visszautalni az alapokhoz. Sok dolgot talán már túl is magyaráztam. Ezt a leírást nem 1 nap alatt készítettem el, kb. 1 hónapja írtam meg az elméleti oldalát, most az elmúlt pár napban pedig a gyakorlatot. Esténként 1-2 órát szántam rá csupán, és mivel ilyenkor már nem vagyok épp a toppon, elképzelhetõ, hogy néhány helyen helyesírási, vagy elvi hibát vétettem. Amennyiben valaki ilyet találna, kérem szóljon, és korrigálni fogom. Nos, azt hiszem nincs is más hátra, mint hogy sok szerencsét kívánjak a bitmûveletek elsajátításához, és megosszam a forrásokat. Remélem néhányan hasznosnak találják, eme igen hosszúra sikerült leírást.4. Források: Internet:WikipédiaSA:MP WikiSA:MP hivatalos fórumKönyv, e-book:Pohl László - A programozás alapjai(BME - Elektronikus Eszközök Tanszéke, Budapest, 2010)Siroki László - C programozás - Bitmûveletek jegyzeteˆ Anthony, 2012 - Minden jog fenntartva.A leírás egészben, vagy részleteiben szabadon terjeszthetõ, ameddig egyértelmûen fel van tüntetve az eredeti készítõ, valamint hogy honnan is származik. « Utoljára szerkesztve: 2012. augusztus 01. - 10:53:54 írta Anthony » Naplózva
00000000000000000000000000000011 ^ 00000000000000000000000000000101 = 0000000000000000000000000000011000000000000000000000000001110110 ^ 00000000000000000000000001011001 = 00000000000000000000000000101111[/quote]Ezzel kapcsolatban egy érdekességet mutatnék meg. Vegyünk 2 számot. A látványosság kedvéért én példának a 118-at és a 89-et veszem. 1. szám: 00000000000000000000000001110110 = 1182. szám: 00000000000000000000000001011001 = 89Mûvelet elvégzése: 00000000000000000000000001110110 ^ 00000000000000000000000001011001 = 00000000000000000000000000101111 = 471. szám az eredmény, 2. szám az eredeti 2. szám: 00000000000000000000000001011001 = 89Eredmény: 00000000000000000000000000101111 ^ 00000000000000000000000001011001 = 00000000000000000000000001110110 = 118[/quote]Vagyis visszakaptuk a kiindulási állapotot 1.4.4 Bitenkénti (bitek közötti) NEGÁLÁSA bitenkénti NEGÁLÁS (NOT, ~) az utolsó logikai operátor, melyet meg fogok mutatni. Ez az operátor eléggé eltér az eddigiektõl, leginkább abban, hogy ezt a mûveletet nem 2 bitre alkalmazzuk, hanem csak 1-re. Szokás komplementer-operátornak is hívni(konyhanyelven). Lényegében az 1-esekbõl 0-t, a 0-kból 1-est csinál. Igazság táblázata: A ~A0110[/quote]Példának nézzük meg, mi történik, ha a bitenkénti NEGÁLÁST alkalmazzuk a 7-es számra. A 7 bináris számrendszerben mint már írtam 00000000000000000000000000000111-nek felel meg. Tehát a ~7, az ugyaz mint ha ezt írnám: ~00000000000000000000000000000111. Nézzük tehát, mi történik: ~0000000000000000000000000000011111111111111111111111111111111000[/quote]Eredményül most 11111111111111111111111111111000-t kaptunk, ami a kettes számrendszerben a -8-nak felel meg. A következõ 2 sor tehát ugyanazt jelenti: Bin:~00000000000000000000000000000111=11111111111111111111111111111000Dec:~7=-8[/quote]Megint néhány példa: ~00000000000000000000000000000101 = 11111111111111111111111111111011~00000000000000000000000001110110= 11111111111111111111111110001010[/quote]Ehhez kapcsolódóan lenne itt egy fontos megjegyzendõ dolog. Aki nem érti, hogy miért -8 a megoldás, az nem olvasta el elég rendesen a \"Részletesebben a bitekrõl a Pawn-ban\" részt.1.4.5 Biteltolás (arithmetic shift, shiftelés) operátor(ok)A shift operátorok(<<, >>) a bitek tologatására használhatóak. A << balra tol, a >> jobbra. Bal oldalt található a szám, amit tologatni kívánunk, jobb oldalt pedig az, hogy mennyivel szeretnénk eltolni.1.4.5.1 Jobbra toló operátor(>>):Ez az operátor a szám bitjeit jobbra tolja a megadott mértékkel. Egy bittel való eltolás, 2-vel való osztásnak felel meg. Nézzünk egy példát: 00000000000000000000000000001000//8>>200000000000000000000000000000010//2[/quote]Láthatjuk, hogy semmi más nem történt, mint minden bitet 2-vel hátrébb mozgattunk. Az így \"megüresedett\" bitek helyére pedig 0-k kerültek. Lényegében az történt, hogy az utolsó 2 bit kiesett, az így megmaradt biteket 2-vel hátrébb csúsztattuk, majd az elsõ 2, így \"üres\" bit 0 értéket kapott.Nézzünk egy példát negatív számmal: 11111111111111111111111111110000//-8>>211111111111111111111111111111100//-2[/quote]Láthatjuk, hogy itt fontos szerepe van az MSB-nek. Ettõl függ ugyanis, hogy 0-k vagy 1-esek kerülnek a \"megüresedett\" bitek helyére. Fontos tehát megjegyezni, hogy a jobbra shiftelés megtartja a számok elõjelét, nem változtat azon!1.4.5.2 Balra toló operátor(<<):Ez az operátor a szám bitjeit balra tolja a megadott mértékkel. Egy bittel való eltolás, 2-vel való szorzásnak felel meg. Nézzünk egy példát: 00000000000000000000000000001000//8<<200000000000000000000000000100000//32[/quote]Itt megint könnyedén észrevehetõ mi is történt. Minden bitet fentebb \"toltunk\" 2-vel. Azok a bitek, amelyek ezáltal a 32. bit elé kerültek, kiesnek. Az így megüresedett bitek 0-kkal egészültek ki. Eddig nem túl bonyolult. Azonban figyeljünk oda, ha túl sokáig shiftelünk egy pozitív számot, akkor az utolsó érték, melyet fel tud venni a változónk (mielõtt még túlcsordulás miatt elérné a 0-t), az egy negatív szám!Nézzük mi történik negatív számok esetén: 11111111111111111111111111111000//-8<<211111111111111111111111111100000//-32[/quote]Láthatjuk hogy a balra shifteléskor a szám mindig 0-kkal egészítõdik ki, legyen az akár negatív, akár pozitív. Nem veszi figyelembe az MSB értékét, így ha túl sokat shiftelünk, akkor elõfordulhat, hogy a szám elveszti negatív értékét, és eléri a 0-t.1.4.6 Logikai biteltolás (logical shift, logikai shiftelés) operátorElõször is le kell tisztázni, hogy logikai shiftelésbõl csak egy létezik, jobbra logikai shiftelés(>>>). Sokban hasonlít a jobbra shiftelõ operátorra (>>), azonban van egy fontos különbség.Nézzük az elsõ példát: 00000000000000000000000000001000//8>>>200000000000000000000000000000010//2[/quote]Eddig nem igazán tapasztalhattunk semmi változást, minden pont ugyanúgy zajlott, mint a jobbra shiftelésnél(>>).Nézzük mi történik negatív szám esetén: 11111111111111111111111111111000//-8>>>200111111111111111111111111111110 //1073741822[/quote]Itt már láthatjuk a különbséget. Logikai shiftelés esetén az üres bitek helyére bekerülõ érték nem függ az MSB-tõl, hanem mindig 0. Emiatt soha nem kaphatunk eredményül negatív számot, logikai shiftelés esetén.1.5 Elsõ fejezet végeNos, ezennel átvettünk mindent, amit érdemes tudni a bitmûveletekrõl. Megmutattam az alapok, hogy hogyan épülnek fel a változók, valamint átvettük a fõ operátorokat. Eddig csak színtiszta elmélet volt. A következõ részben megmutatom, hogyan hasznosíthatjuk mindezt a gyakorlatban, vagyis mire is jó az a sok marhaság, amit eddig leírtam.2. Gyakorlati példák, érdekességek 2.1 Áttekintés, mikrõl is lesz még szóAz alábbiakban még pár dolgot szeretnék mutatni. Elõször is néhány \"függvénnyel\" fogok foglalkozni, majd mutatok még egy-két érdekességet. Konkrétan megmutatom, hogyan tudjuk egy jármû minden \"paneljének\" adatát kiíratni, részletesen(erre alapból nincs lehetõség, és a függvények is mind \"encode\" függvények, én viszont mutatok egy-egy decode \"verziót\" is.)Az alábbi 4 függvénnyel fogok foglalkozni: encode_tiresencode_panelsencode_doorsencode_lightsDe elõbb egy kis háttérinfó:A Pawnban a forrásban megadhatunk bináris számokat ugyanúgy, ahogyan Hex számokat is megadhatunk. Ehhez a \"0b\" prefixet kell használnunk. Tehát ha bináris számunk az 1011 és ezt akarjuk adni értékül egy változónak, azt így tehetjük meg: new var = 0b1011; 2.2 encode_tiresHáttér információ:A SA:MP-ban egy jármû abroncsainak adataid 4 bit tárolja. Az elsõ bit a jobb hátsó, a második bit a jobb elsõ, a harmadik bit a bal hátsó, a negyedik bit pedig a bal elsõ abroncsról tárol információt. Mivel 4 bitben eltárolhatóak az információk, így decimális formában 0-15 értékekkel is be lehet állítani a jármûvek abroncsait. 16-tól kezdve az értékek már ismétlõdni kezdenek, szóval a 16-nál az eredmény ugyanaz mint 0-nál, 17-nél ugyanaz mind 1-nél, és így tovább. Ez érthetõ, ha tudjuk hogyan épül fel a kettes számrendszer.(Ne felejtsük el, a biteket hátulról számozzuk!)Nos nézzük hogyan néz ki a kód: encode_tires(tire1, tire2, tire3, tire4){ return tire1 | tire2 << 1 | tire3 << 2 | tire4 << 3;} Láthatjuk, hogy bekérjük a 4 abroncs adatát(vagyis hogy ép[0], vagy kidurrant[1]), majd egy sima bitenkénti VAGY mûvelet és biteltolás kombinálásával visszatérünk az eredménnyel. A kódban a tire1 felel meg a jobb hátsó abroncsnak, a tire2 a jobb elsõ, a tire3 a bal hátsó, és a tire4 a bal elsõnek. Nézzünk egy konkrét példát. Tegyük fel, hogy én a hátsó 2 abroncsot ki akarom durrantani, még az elsõ kettõt épre akarom beállítani. Ez esetben így néz ki a dolog: encode_tire(1,0,1,0); Ha kiíratnánk konzolba a visszatérési értéket (%d-vel), 5-öt kapnánk. Miért is? Az 5 bináris alakja 0101. Hoppá, ha megnézzük ez pont fordítottja annak, amit mi megadtunk számjegyekként, csak már konkrét bináris számként. Nézzük hogyan is történhetett mindez, mit is csináltunk. A függvény a következõ mûveletet végzi: tire1 | tire2 << 1 | tire3 << 2 | tire4 << 3 Konyhanyelven úgy magyarázhatnánk el a dolgot, hogy ez a kód egymás mellé pakolgatja a megadott számokat, fordított sorrendben. Na de mi most nem a konyhanyelv miatt vagyunk ebben a leírásban, szóval nézzük a kódunkat. Lényegében ez futott le, a konkrét értékeinkkel nézve: 1 | 0 << 1 | 1 << 2 | 0 << 3 Bármily furcsa is, ez egy mûvelet, melyen szépen végig halad sorban a program. Elõször is írjuk át az egészet bináris alakba(az egyszerûség kedvéért most csak 4 bitet fogok kiírni): 0001 | 0000 << 1 | 0001 << 2 | 0000 << 3 Na erre se mondtuk volna rá a tutorial elsõ részének elolvasása elõtt, hogy ez egy mûveletsor. A rendszer elsõnek az eltolásokat végzi el (olyan ez mint a 2*3+2*4+2*5 mûveletben elõször a szorzásokat végezzük el, és csak utána adunk össze, ha nincs zárójel), és csak utána a bitenkénti VAGY mûveleteket, ezért mi is így fogunk tenni. Emlékezzünk, eltoláskor az \"üres\" bitek 0 értéket fognak felvenni. Végezzük el külön csak az eltolásokat: 0000 << 1 = 00000001 << 2 = 01000000 << 3 0000 Nos, ha ezeket elvégeztük, akkor nézzük, hogy is áll most a kódunk: 0001 | 0000 | 0100 | 0000 Négy azonos mûvelet van hátra, így hát a program innen más szépen sorban fog haladni. Tegyünk mi is így. Elsõ tehát a 0001 | 0000 mûvelet elvégzése. Emlékeztetõül, a bitenkénti VAGY mûvelet akkor ad 0-t eredményül, ha mind a kettõ érték 0, minden más esetben 1-et. Végezzük el külön az elsõ bitenkénti VAGY mûveletet: 0001 | 0000 = 0001 Nem volt bonyolult, nézzük tehát hogy áll akkor most a teljes mûveletünk: 0001 | 0100 | 0000 Haladunk szépen tovább, elvégezzük a 0001 | 0100 mûveletet. 0001 | 0100 = 0101 Nincs mit magyarázni rajta, egyszerû bitenkénti VAGY mûvelet volt ez is. Már csak ez van hátra: 0101 | 0000 Nos, ez sem lesz egy bonyolult mûvelet: 0101 | 0000 = 0101 Ezzel meg is kaptuk a végeredményt, ami nem más, mint a 0101 (tízes számrendszerben 5). Mivel azonban eléggé kusza lett így a számításunk, nézzük egyben hogyan is néz ki az egész, ha nem bontom ennyire részekre: 0100 | 0000 << 1 | 0001 << 2 | 0000 << 3v0001 | 0000 | 0100 | 0000v0001 | 0100 | 0000v0101 | 0000v0101 Nos, ezennel láthattuk a gyakorlatban is, hogyan mûködik a függvény. Kijelenthetjük tehát, hogy a függvény a számokat a sorrendjüknek megfelelõ számú bitbe helyezi. Mivel azonban a biteket hátulról számozzuk, így az 1,0,1,0 bemenetek végeredménye 0101 lesz. Így már nem is olyan bonyolult Na, ha már ilyen sokáig eljutottunk, akkor felhasználhatjuk ezt a kódot arra, hogy az alapjait figyelembe véve elkészítsük a decode változatot. Mivel 4 értékkel térünk vissza, ezért paraméterként kell majd 4 változó, melyeknek módosíthatjuk az értékét, illetve 1 paraméter pedig a dekódolandó szám. A függvényünk \"címsora\" tehát így néz ki: decode_tires(tires, &tire1, &tire2, &tire3, &tire4) Hogy példánk ne csak elmélet legyen, a tires változó legyen az elõbbi 0101 értékkel egyenlõ. Na akkor menjünk tovább. Bemeneti értékünk ugye a \"tires\" változóban lesz eltárolva, így ezzel fogjuk a mûveleteket végezni. Kezdjünk el gondolkozni. Hogyan is tudnánk ellenõrizni egy adatot. Legegyszerûbb ha a bitenkénti ÉS mûveletet alkalmazzuk. Miért is? Nos, a bitenkénti ÉS mûvelet ugye akkor ad eredményül 1-et, ha mind a 2 érték egy. Tehát ha min egy számot összehasonlítunk a 1-el(binárisan ugye 00000000000000000000000000000001, akkor az eredményünk lényegében arról fog tájékoztatni, hogy az elsõ bit 0, vagy 1. Nem is olyan bonyolult ez. Nézzük akkor az elsõ bitet. Arra emlékezhetünk még, hogy az elsõ bit a tire1, vagyis jobb hátsó abroncs adatait tárolja. Tehát: tire1 = tires & 1; Vagy konkrétan a példánk esetében: tire1 = 0101 & 0001 Mivel a bitenkénti ÉS mûvelet akkor ad 1-et eredményül, ha mind a kettõ bit 1, akkor már láthatjuk is, hogy miért elegendõ az elsõ bitet szemügyre venni. Mivel mind a 2 helyen az elsõ bit 1, ezért a tire1 változónk értéke is 1(0001) lesz. Egyszerû és nagyszerû, máris megkaptuk a jobb hátsó abroncs állapotát.Gondolkozzunk hát tovább. Nekünk most arra van szükségünk, hogy a 2. bit értékét megkapjuk. Hogyan kaphatjuk ezt meg? Legegyszerûbb megoldás, ha a tires változót jobbra shifteljük 1-el, majd ismételten bitenkénti ÉS mûveletet végzünk. Nézzük tehát, hogyan is nézne ez ki: tire2= tires >> 1 & 1; Vagy konkrétan a mi példánkban: tire2 = 0101 >> 1 & 0001vtire2 = 0010 & 0001 Ismételten bitenkénti ÉS mûvelet, és az eredményünk 0, mivel csak az egyik bit 1, a másik 0, így a bitenkénti ÉS mûvelet eredménye is 0. Ezen az elven megcsináljuk a maradék 2 abronccsal is: tire3= tires >> 2 & 1;tire4= tires >> 3 & 1; Vagy a mi esetünkben konkrét példánkkal: tire3 = 0101 >> 2 & 0001 // azaz: --> 0001 & 0001 = 1tire4 = 0101 >> 3 & 0001 // azaz: --> 0000 & 0001 = 0 A teljes kódunk tehát: decode_tires(tires, &tire1, &tire2, &tire3, &tire4){ tire1 = tires & 1; tire2 = tires >> 1 & 1; tire3 = tires >> 2 & 1; tire4 = tires >> 3 & 1;} A konkrét példánkban a 4 változónk értéke a függvény lefutása után: tire1: 1tire2: 0tire3: 1tire4: 0 Ha összehasonlítjuk, ezek pont azok az értékek, mint amiket az encode függvényben megadtunk, tehát úgy tûnik jól dolgoztunk 2.3 encode_lightsEz a rész lesz a legrövidebb. Az egész lámpás rész pontosan ugyanarra a sémára épül mint az abroncsok. Az adott bitek ugyanazon a \"helyen\" lévõ lámpa adatait szolgáltatják, ugyanúgy 0 az ép, és 1 a sérült lámpák állapot azonosítója, és ugyanúgy 1-1 bit tárolja az értékeket.Maga a kód: encode_lights(light1, light2, light3, light4){ return light1 | light2 << 1 | light3 << 2 | light4 << 3;} Láthatjuk hogy halál pontosan ugyanaz mint az encode_tires, csak más változónevekkel. Így hát a decode függvény is ugyan az lesz, csak a változóneveket kell módosítanunk: decode_lights(lights, &light1, &light2, &light3, &light4){ light1 = lights & 1; light2 = lights >> 1 & 1; light3 = lights >> 2 & 1; light4 = lights >> 3 & 1;} Ha megnézzük látjuk, hogy teljesen ugyan az a séma, ugyan az a felépítés, minden megegyezik, ezért nem is írtam le újból ugyanazt, amit egyszer már kifejtettem.2.4 encode_panelsHáttér információ:A SA:MP 7 panelrõl tárol adatokat. Egy panel adatait 4 bit írja le. Nos, ennél több infóra nekünk most nincs szükségünk, az elõzõ kód alapján ezen kód is megérthetõ, így az encode verzióhoz nem is nagyon szeretnék magyarázatot fûzni. A különbség az encode_tires-hez képest, hogy itt 4-essével shiftelünk, mivel 1 panel adatait 4 bit tárolja.Maga a kód: encode_panels(flp, frp, rlp, rrp, windshield, front_bumper, rear_bumper){ return flp | frp << 4 | rlp << 8 | rrp << 12 | windshield << 16 | front_bumper << 20 | rear_bumper << 24;} Nézzünk egy decode verziót, ehhez a kódhoz. Alapjaiban ugyanazon a vonalon kell elindulnunk, mint a decode_tires esetében. Itt most 7 visszatérési értékünk lesz, és 1 bemeneti érték. Tehát így néz ki a függvény header: decode_panels(panels, &flp, &frp, &rlp, &rrp, &windshield, &front_bumper, &rear_bumper) A folytatásban megint csak az elõbbi verziót kell alapul vennünk, azonban itt lesz egy kis különbség. Egyértelmû, hogy mivel 1 panel adatait 4 bit tárolja, így a 4 bitet nekünk egyben kell lekérni, és egyben kell vele visszatérnünk. Megint csak a bitenkénti ÉS mûveletet fogjuk alkalmazni, azonban mivel 4 bitrõl van szó, így egy olyan számmal kell összehasonlítanunk, mely bináris alakban 1111. Ez a szám nem más, mint a 15. Hogy miért ez kell?Ha egy bitsorozat tartalmát ki szeretnénk másolni egy másik változóba, akkor a bitenkénti ÉS mûveletre vna szükségünk. A bitenkénti ÉS mûvelet akkor ad 1-et eredményül, ha mindkét bit 1, ha valamelyik 0, akkor a visszatérés már 0 lesz. Ergó tehát, ha egy csupa 1-esekbõl álló bitsorozattal vetjük össze az eredeti bitsorozatunkat, akkor lényegében lemásoljuk a sorozatot, egy másik változóba, hisz eredményül mindig az eredeti sozatot fogjuk visszakapni. Íme néhány példa: 0101 & 1111 = 01011111 & 1111 = 11110000 & 1111 = 00001011 & 1111 = 1011 Láthatjuk tehát, hogy ha csupa 1-est tartalmazó bitsorozattal hasonlítjuk össze az eredeti számunkat, akkor lényegében egy másolatot készítünk. Mivel nekünk erre a 4 bitre van szükségünk a 32-bõl, így pontosan ezt kell tennünk, ezen 4 bitet lemásolni egy másik változóba, hogy késõbb ezt majd egy másik függvénnyel még jobban darabokra szedhessük.Íme tehát akkor a kódunk elsõ sora: flp = panels & 15; Mivel 4-esével vizsgáljuk a biteket, így lényegében annyit kell még változtatnunk az elõzõ kódon(decode_tires), hogy az 1-esével növekvõ shiftelések helyett 4-esével shifteljünk. Azaz a 2. panel adatainak lekérdezése: frp = panels >> 4 & 15; Innen pedig már csak mindig további 4 shiftelést el kell végeznünk, és szépen végig haladhatunk a panels változó bitjein, és megkaphatjuk minden panel adatait külön-külön. rlp = panels >> 8 & 15;rrp = panels >> 12 & 15;windshield = panels >> 16 & 15;front_bumper= panels >> 20 & 15;rear_bumper = panels >> 24 & 15; Ezennel minden panel adatait megkaptuk. Ezután már akár a decode_tires függvényt felhasználva szét lehet szedni az adatokat darabokra, de akár egy saját névvel, és más változó nevekkel ellátott másik függvényt is létre lehet hozni a decode_tires mintájára.Tehát az egész kód egyben: decode_panels(panels, &flp, &frp, &rlp, &rrp, &windshield, &front_bumper, &rear_bumper){ flp = panels & 15; frp = panels >> 4 & 15; rlp = panels >> 8 & 15; rrp = panels >> 12 & 15; windshield = panels >> 16 & 15; front_bumper= panels >> 20 & 15; rear_bumper = panels >> 24 & 15;} Igazából a függvény ugyanarra a sémára épül mint a decode_tires, csak annyi a különbség, hogy 4 bitenként kapjuk az adatokat, nem pedig egyesével, mivel itt minden panelnek 4 adatát tudjuk majd még lekérdezni. Nem tudom lehet-e még ehhez a részhez hozzáfûzni valamit, szóval szerintem ugorjunk. 2.5 encode_doorsHáttér információ:A SA:MP képes minden jármû 4 \"ajtajának\" állapotát tárolni. Ezek az \"ajtók\"(Doors-t hogy fordíthatnám jobban?) konkrétan a motorháztetõ, csomagtartó, vezetõoldali ajtó, és anyósülés oldali ajtó. A 2 hátsó ajtó állapotát jelenleg még nincs lehetõségünk lekérdezni.Amikor lekérdezzük egy jármû \"ajtajának\" adatait, azt a SA:MP 4 bájtként(32 bit) adja vissza. Ebbõl a 4 bájtból 1 bájt(8 bit) jut minden egyes objektumra. Vagyis minden egyes objektum adatait 8 bit tárolja. Vagyis tárolhatná...A SA:MP csak az elsõ 3 bitet használja, a további bitek üresek lesznek, nincs funkciójuk, talán majd késõbb lesz. Az elsõ bit azt tárolja, hogy az objektum zárt[0], vagy nyitott[1](nem az jelenti, hogy bezárt, hanem becsukott/kinyitott) állapotban van-e. A második bit azt adja meg, hogy az objektum ép[0] vagy sérült[1]. A 3. bit arról tájékoztat, hogy az adott objektum rajta van-e még a jármûvön[0] vagy már leesett[1]. A többi bit mint mondtam üres. Ez majd a decode függvénynél lesz fontos.Nézzük tehát a konkrét kódot: encode_doors(bonnet, boot, driver_door, passenger_door, behind_driver_door, behind_passenger_door){ #pragma unused behind_driver_door #pragma unused behind_passenger_door return bonnet | boot << 8 | driver_door << 16 | passenger_door << 24;} A 2 hátsó ajtóval nem foglalkozunk, mivel azok jelenleg még nem támogatottak ugyebár, de a kód készítõje gondolt a jövõre is, vagy fogalmam sincs miért adta meg ezt a két paramétert is, hisz ígyis-úgyis módosítani kell a kódon... Ha behoznak még 2 ajtót, akkor az 1 bájtos tárolással már 6 bájt lenne, de 1 változó maximum 4 bájt lehet, tehát a kódot úgyis módosítani kell, ha meg arra gondoltak, hogy legalább a paramétereket nem kell majd szegény scriptereknek minden sorban átírni, az megint hülyeség, hisz ki adna értéket olyan paraméternek, amely nincs használva?...Nos mindegy, nem a kritizálásért vagyunk most itt. Ha megnézzük, a kód 8-as shiftelést használ, mivel egy objektum adatait 8 bit tárolja. A mûködési elv ugyanaz, mint az elõzõ encode függvényeknél, így erre nem kívánok megint kitérni.Nézzük azonban hogyan írnánk ehhez egy decode függvényt. Mikor megkérdeztem errõl ismerõsömet, õ azt mondta, ugyanúgy mint az elõzõ, decode_panels függvénynél, csak 8-as shiftelést használunk, és 15 helyett 255-el végezzük a bitenkénti ÉS mûveletet. 255-el? minek?No erre is ki fogok térni késõbb, de haladjunk a szokásos módon. 1 bemeneti paraméterünk van, és 4 kimeneti, vagyis a header rész a következõ: decode_doors(doors, &bonnet, &boot, &driver_door, &passenger_door) Ezzel meg is vagyunk, itt nem látok különösebb magyarázni valót. Én most nem fogok törõdni a 2 nem létezõ ajtóval, ha egy jövõbeni verzióban netán azokat is behoznák mint lekérdezhetõ objektum, akkor ígyis-úgyis módosítani kell majd a kódot, addig meg úgysincs rá szükségünk.Nos, mint az eddigi decode függvényeknél, elsõ feladatunk egy shiftelés mentes bitenkénti ÉS mûvelet elvégzése. Az egyik megoldás a következõ: bonnet = doors & 255; Lehet így is. Azonban véleményem szerint felesleges itt egy ekkora számmal \"szenvednünk\". Miért használjunk ekkora számot, és ezáltal bonyolítsuk feleslegesen a kódunkat, ha nekünk a 8 bitbõl csak az elsõ 3 lesz ami adatot tárol? Bõven elegendõ egy olyan szám, amely bináris alakja 111. Ez nem más, mint a 7. Tehát a következõ megoldás ugyanúgy helyes: bonnet = doors & 7; A két kód pontosan ugyanazt az eredményt fogja visszaadni, semmi különbség a kettõ között. Csupán elsõ esetében nagy számmal dolgozunk, még második verzió esetén csak akkorával, amekkora szükséges.A kód többi részében csak 8-as shifteléssel végigcammogunk a doors változó többi bitjén is: boot = doors >> 8 & 7;driver_door = doors >> 16 & 7;passenger_door = doors >> 24 & 7; A teljes kód egyben: decode_doors(doors, &bonnet, &boot, &driver_door, &passenger_door){ bonnet = doors & 7; boot = doors >> 8 & 7; driver_door = doors >> 16 & 7; passenger_door = doors >> 24 & 7;} A kód pont ugyanazon az elven épül fel, mint az összes többi decode függvényünk, csak épp a benne használatos értékek az \"ajtó\" objectumok adatstruktúrájához vannak igazítva.2.6 Egyéb ötletek gyakorlati alkalmazásra, hasznosításraJoggal felmerülhet a kérdés, hogy ennyi? Emiatt rágtuk végig magunkat ezen néhány tízezer karakteren? Nem. Nem ennyi. A SA:MP-ban rengeteg felhasználási módja lehet még a bitmûveleteknek. Mutatok is néhány dolgot, mire jók még.2.6.1 RGBA->HEX; HEX->RGBA konvertálásokNos, bármily \"hihetetlen\", erre is használható. Miért is? Nos, 16-os számrendszerben 0-9, valamint A-F \"szánjegyek\" léteznek. A hexadecimális számjegy decimális értékben 0-15 lehet. Ha megnézzük bináris értékeket, 0-15 az 4 bittel leírható, hiszen a 15 az bináris alakban 1111. De ha józan paraszti ésszel belegondolunk is. 16 az 24. Így tehát 1 hexadecimális érték, az 4 bináris értékkel írható le. Ebbõl már sejthetõ, hogy itt is shiftelésre lesz szükségünk. Naná Mivel 4 bit ír le egy hexadecimális számjegyet, így 4-esével kell majd shiftelnünk.Mi is az az RGBA?RGBA: A színek olyan meghatározási módja, melyben a 3 alapszín([r]ed, [g]reen, lue) mellett egy 4. érték, az úgynevezett [a]lpha is szerepel, amely az áttetszõséget adja meg. A legtöbb program(pl. paint, PS), és pl. a css(külön függvénnyel!) is elfogadja ezen értékeket decimális számokként(0-255) is, a SA:MP-ban azonban hexadecimális alakban kell megadnunk. Bármily furcsa a következõ 4 sor pont ugyan azt jelenti:\"Normál\" RGB alak(pl. paint, photoshop): Piros: 0, Zöld: 255, Kék: 255CSS alak(függvénnyel): rgba(0, 255, 255, 1);Pawn alak(1): 0x00FFFFFFPawn alak(2): 16777215 Ó igen, a Pawn-ban decimális számokként is megadhatjuk a színeket, hisz a program mindent bináris számrendszerbe fog átszámolni. De akár bináris alakban is megadhattuk volna:Pawn alak(3): 0b111111111111111111111111 Az, hogy mi a Pawn-nak hexadecimális alakban szoktuk megadni, annak az egyetlen oka, hogy így a leginkább \"átlátható\", és ez egy amúgy is elfogadott módja (mármint a hexadecimális számmal való megadás) a színek leírásának a számítástechnikában.Nos, mivel 1 szín 0 és 255 közötti értéket tartalmaz a piros, zöld és kék színekbõl, ez hexadecimálisan 2 számjeggyel leírható: 00-FF. Az FF értéke 255, aki nem hiszi, számoljon utána (162-1)2 hexadecimális számjegy az pedig 8 bináris számjeggyel leírható(00000000-11111111). Így már láthatjuk, hogy 8-as shiftelésre lesz szükségünk. Másra nincs is szükségünk, az RGBA->HEX konverter elkészítéséhez.stock RGBAToHex( r, g, b, a ){return a | b << 8 | g << 16 | r << 24;} Az, hogy miért fordított sorrendben kellett megadnunk az értékeket, az megint csak azzal magyarázható, hogy a biteket hétköznapi értelemben véve visszafelé számozzuk.Egy ellentét, azaz HexToRGBA függvény megírása sem túl bonyolult, ugyan ezt kell csinálnunk, csak visszafelé.stock HexToRGBA( colour, &r, &g, &b, &a ){a = colour & 0xFF;b = (colour >> 8 ) & 0xFF;g = (colour >> 16 ) & 0xFF;r = (colour >> 24 ) & 0xFF;} A 0xFF-et természetesen decimális értékkel is megadhatjuk(255).No akkor lehet ugye RGBA-t ARGB-be is konvertálni, az így néz ki:RGBAToARGBHEX(r,g,b,a){return (r & 0xff) << 24 | (g & 0xff) << 16 | (b & 0xff) << 8 | a & 0xff;} Ez máris egy újabb felhasználási módja a bitmûveleteknek. De, mutatok még 2.6.2 Boolen változók helyett IntegerAz al-al-alcímet elolvasva joggal felmerülhet a kérdés, hogy ez meg hogy jön ide. Nos ez úgy jön ide, hogy most egy kis memóriatakarékosságról fogok itt regélni. Adhattam volna azt is címnek, hogy miért ne használjunk boolean változókat, ha legalább 5 igaz/hamis értéket el akarunk tárolni.Nos akkor egy kis memórialecke. Azt már tisztáztuk, hogy az integer változók 32 bitesek, vagyis 4 bájtot foglalnak le a memóriában maguknak. A boolean típusú változókról azonban még nem volt szó. Mivel 1 értéket tárolnak csak, az is csak igaz vagy hamis lehet, így joggal gondolhatnánk, hogy 1 bit a mérete. Azonban nem...1 boolean változó 8 bitet, azaz 1 bájtot foglal le magának a memóriában. Hogy ez miért így van, az a számítógépek memóriahasználatával magyarázható. Manapság nincs mód arra, hogy a memóriában egyetlen bitet módosítsunk, csak 8-as \"csomagokban\", vagyis bájtonként tudjuk ezt megtenni. Hogy ez miért van így, azt most nem fogom itt fejtegetni Nos, ha megnézzük tehát, a boolean változó egy nagyon-nagyon pazarló megoldás. Hiszen 1 darab boolean változó 1 bájtot foglal, 4 darab boolean változó esetén ez már 4 bájt, ami megegyezik 1 integer változó méretével. Azonban, még a 4 boolean változóban 4 igaz/hamis érték kerülhet tárolásra, addig egy integerben 32 igaz/hamis értéket is eltárolhatunk.Bizony, 32 igaz/hamis érték. Hiszen 1 integer változó 32 bitbõl áll, ha minden bitet hasznosítunk, akkor a bitmûveletek felhasználásával 32 igaz/hamis értéket is eltárolhatunk. Összehasonlítva:32 darab boolean változó mérete 32 bájt.1 integer változó mérete: 4 bájt. Vagyis 32 boolean változó esetén nyolcad annyi memóriánkba kerül, ha egy darab integerben tároljuk el az értékeket. Persze 32 booleanra ritkán van szükség, de egy RP modban simán elõfordulhat. Gondoljunk csak bele... Van 32 boolean-tömbös változónk, ez a maximum slotszámot(1000) figyelembe véve: 32*1000 byte azaz 32000 byte ami ~31 kilobyte. Ha ezt egyetlen integer-tömbbel oldanánk meg: 4*1000 byte, azaz 4000 byte, ami ~3,9 kilobyte. Azért így nézve már nem annyira \"elhanyagolandó\" az a különbség szerintem =)Akár írhatnánk erre külön egy függvényt, mely az adott bit értékével tér vissza. Nem is bonyolult. Így már azok számára is használhatóak a bitmûveletek, akik nem értenek hozzá, és rengeteg memóriát takaríthatnak meg.Íme egy függvény az adatok kiolvasására:stock GetData(integer, bit){ return integer >> bit & 1; //vagy az 1 helyett akár 0x01 alakban is megadhatjuk, nincs jelentõsége} És a párja, adat írásra:stock SetData(&integer, bit, value){ integer |= value << bit; return 1;} Persze ezt még lehetne csinosítani, hogy pl. ne 0-tól hanem 1-tõl számoljuk a biteket, vagy hogy 31(vagy 32 ha 1-tõl számolunk) felett 0 legyen a return értéke, esetleg a value-re is rakhatnánk ellenõrzést, hogy 0-1-e az értéke, vagy más, de ilyenekkel most nem törõdtem.2.6.3 Több érték, egy integerbeNa ez az a rész, ami véleményem szerint már csak a nagyon megrögzött memóriafelszabadítók szerint nem lesz felesleges Amennyiben nincs szükségünk túl nagy számokra(pl. csak 0-15, vagy akár csak 0-7, de akár még a 0-255 is megfelel), akkor akár egy integerbe is tehetjük õket. Hogyan? Egyszerû. Csoportosítjuk a biteket. Tegyük fel, hogy maradván egy RP szervernél, el akarjuk tárolni ûbergigantikusan memóriakímélõ megoldással a játékos házának számát(ez legyen mondjuk 0-100 közötti szám), a játékos frakcióját(ez legyen 0-15 között), a játékos rangját(0-7 között), hogy a telefonja be van-e kapcsolva(0-1), a játékos szolgálati jármûvének id-jét(legyen 0-255 között), és az alapmunkáját(0-15 között). Ez is megoldható, egyetlen integerben, és ismételten 1000 slottal számolva máris megspóroltunk 4*4*1000+1*1000 byte-t(5 integer, és 1 boolean helyett csupán 1 integert használunk), ami ~16,6 kilobyte. Azért nem mindegy az, hogy 20,5 kb-ot vagy 4 kb-ot használunk szerintem... Ezt a következõ módszerrel valósíthatnánk meg:#define DATA_HOUSE 0#define DATA_FACTION 1#define DATA_RANK 2#define DATA_PHONESTATE 3#define DATA_DUTYCAR 4#define DATA_JOB 5stock GetData(playerinfo, data){ switch(data) { case DATA_HOUSE: return playerinfo & 127;//7 bit case DATA_FACTION: return playerinfo >> 7 & 15;//4 bit case DATA_RANK: return playerinfo >> 11 & 7;//3 bit case DATA_PHONESTATE: return playerinfo >> 14 & 1;//1 bit case DATA_DUTYCAR: return playerinfo >> 15 & 255;//8 bit case DATA_JOB: return playerinfo >> 23 & 15;//4 bit}return -1;}stock SetData(&playerinfo, data, value){ switch(data) { case DATA_HOUSE: { playerinfo = playerinfo & ~127 | value; }//7 bit (0-127 közötti maximálisan) case DATA_FACTION: { playerinfo = playerinfo & ~(15 << 7) | value << 7; }//4 bit (0-15 között) case DATA_RANK: { playerinfo = playerinfo & ~(7 << 11) | value << 11; }//3 bit (0-7 között) case DATA_PHONESTATE: { playerinfo = playerinfo & ~(1 << 14) | value << 14; }//1 bit (0-1) case DATA_DUTYCAR: { playerinfo = playerinfo & ~(255 << 15) | value << 15; }//8 bit (0-255 között) case DATA_JOB: { playerinfo = playerinfo & ~(15 << 23) | value << 23; }//4 bit (0-15 között) } return -1;}stock SetDatas(&playerinfo, house, faction, rank, phonestate, dutycar, job){playerinfo |= house | (faction << 7) | (rank << 11) | (phonestate << 14) | (dutycar << 15) | (job << 23);} Ez már azért elég morbid, ezt inkább csak érdekességképp mutattam, de éppenséggel ez is használható lenne, csak ehhez már kell egy kis õrültség 2.7 Második fejezet végeNos, láthattuk hogyan is mûködik mindazon sok \"marhaság\", amit az elsõ fejezetben elméletként átvettünk. Láthattuk hogyan mûködnek a jármûvek paneljének adataival babráló encode függvények, valamint megmutattam, hogyan írhatunk hozzájuk decode verziókat. Láthattunk még példát egyéb gyakorlati hasznosításra, kicsit megkoccoltuk a hexadecimális számok világát is, majd szóba került egy kis memóriatakarékosság. Végül mutattam egy összetettebb, elvetemült példát is, a bitmûveletek hasznosítására.3. Összegzés, végszó Úgy gondolom, hogy elég részletesen körbe jártuk a témát. Megnéztük a bitmûveletek elméleti, és gyakorlati oldalát is, láthattunk gyakorlati példákat. Nem kell elsõre megérteni ezt az egészet, sõt... Elegendõ ha egyszer elolvassuk, emésztjük kicsit, majd késõbb megint elõvesszük, akkor netán már kicsit többet is megértünk belõle. Nem egy kihagyhatatlan része a Pawno-nak, de azért van annak haszna, ha az ember jártas ebben a témában is, bár sokak szerint nem éri meg a sok fáradtságot, és idõt, mely a megértéséhez, begyakorlásához, használatának elsajátításához szükséges. Próbáltam mindent részletesen kifejteni. A bonyolultabb dolgokhoz sokat magyarázni, az egyszerûbbek esetén csak visszautalni az alapokhoz. Sok dolgot talán már túl is magyaráztam. Ezt a leírást nem 1 nap alatt készítettem el, kb. 1 hónapja írtam meg az elméleti oldalát, most az elmúlt pár napban pedig a gyakorlatot. Esténként 1-2 órát szántam rá csupán, és mivel ilyenkor már nem vagyok épp a toppon, elképzelhetõ, hogy néhány helyen helyesírási, vagy elvi hibát vétettem. Amennyiben valaki ilyet találna, kérem szóljon, és korrigálni fogom. Nos, azt hiszem nincs is más hátra, mint hogy sok szerencsét kívánjak a bitmûveletek elsajátításához, és megosszam a forrásokat. Remélem néhányan hasznosnak találják, eme igen hosszúra sikerült leírást.4. Források: Internet:WikipédiaSA:MP WikiSA:MP hivatalos fórumKönyv, e-book:Pohl László - A programozás alapjai(BME - Elektronikus Eszközök Tanszéke, Budapest, 2010)Siroki László - C programozás - Bitmûveletek jegyzeteˆ Anthony, 2012 - Minden jog fenntartva.A leírás egészben, vagy részleteiben szabadon terjeszthetõ, ameddig egyértelmûen fel van tüntetve az eredeti készítõ, valamint hogy honnan is származik. « Utoljára szerkesztve: 2012. augusztus 01. - 10:53:54 írta Anthony » Naplózva
1. szám: 00000000000000000000000001110110 = 1182. szám: 00000000000000000000000001011001 = 89Mûvelet elvégzése: 00000000000000000000000001110110 ^ 00000000000000000000000001011001 = 00000000000000000000000000101111 = 471. szám az eredmény, 2. szám az eredeti 2. szám: 00000000000000000000000001011001 = 89Eredmény: 00000000000000000000000000101111 ^ 00000000000000000000000001011001 = 00000000000000000000000001110110 = 118[/quote]Vagyis visszakaptuk a kiindulási állapotot 1.4.4 Bitenkénti (bitek közötti) NEGÁLÁSA bitenkénti NEGÁLÁS (NOT, ~) az utolsó logikai operátor, melyet meg fogok mutatni. Ez az operátor eléggé eltér az eddigiektõl, leginkább abban, hogy ezt a mûveletet nem 2 bitre alkalmazzuk, hanem csak 1-re. Szokás komplementer-operátornak is hívni(konyhanyelven). Lényegében az 1-esekbõl 0-t, a 0-kból 1-est csinál. Igazság táblázata: A ~A0110[/quote]Példának nézzük meg, mi történik, ha a bitenkénti NEGÁLÁST alkalmazzuk a 7-es számra. A 7 bináris számrendszerben mint már írtam 00000000000000000000000000000111-nek felel meg. Tehát a ~7, az ugyaz mint ha ezt írnám: ~00000000000000000000000000000111. Nézzük tehát, mi történik: ~0000000000000000000000000000011111111111111111111111111111111000[/quote]Eredményül most 11111111111111111111111111111000-t kaptunk, ami a kettes számrendszerben a -8-nak felel meg. A következõ 2 sor tehát ugyanazt jelenti: Bin:~00000000000000000000000000000111=11111111111111111111111111111000Dec:~7=-8[/quote]Megint néhány példa: ~00000000000000000000000000000101 = 11111111111111111111111111111011~00000000000000000000000001110110= 11111111111111111111111110001010[/quote]Ehhez kapcsolódóan lenne itt egy fontos megjegyzendõ dolog. Aki nem érti, hogy miért -8 a megoldás, az nem olvasta el elég rendesen a \"Részletesebben a bitekrõl a Pawn-ban\" részt.1.4.5 Biteltolás (arithmetic shift, shiftelés) operátor(ok)A shift operátorok(<<, >>) a bitek tologatására használhatóak. A << balra tol, a >> jobbra. Bal oldalt található a szám, amit tologatni kívánunk, jobb oldalt pedig az, hogy mennyivel szeretnénk eltolni.1.4.5.1 Jobbra toló operátor(>>):Ez az operátor a szám bitjeit jobbra tolja a megadott mértékkel. Egy bittel való eltolás, 2-vel való osztásnak felel meg. Nézzünk egy példát: 00000000000000000000000000001000//8>>200000000000000000000000000000010//2[/quote]Láthatjuk, hogy semmi más nem történt, mint minden bitet 2-vel hátrébb mozgattunk. Az így \"megüresedett\" bitek helyére pedig 0-k kerültek. Lényegében az történt, hogy az utolsó 2 bit kiesett, az így megmaradt biteket 2-vel hátrébb csúsztattuk, majd az elsõ 2, így \"üres\" bit 0 értéket kapott.Nézzünk egy példát negatív számmal: 11111111111111111111111111110000//-8>>211111111111111111111111111111100//-2[/quote]Láthatjuk, hogy itt fontos szerepe van az MSB-nek. Ettõl függ ugyanis, hogy 0-k vagy 1-esek kerülnek a \"megüresedett\" bitek helyére. Fontos tehát megjegyezni, hogy a jobbra shiftelés megtartja a számok elõjelét, nem változtat azon!1.4.5.2 Balra toló operátor(<<):Ez az operátor a szám bitjeit balra tolja a megadott mértékkel. Egy bittel való eltolás, 2-vel való szorzásnak felel meg. Nézzünk egy példát: 00000000000000000000000000001000//8<<200000000000000000000000000100000//32[/quote]Itt megint könnyedén észrevehetõ mi is történt. Minden bitet fentebb \"toltunk\" 2-vel. Azok a bitek, amelyek ezáltal a 32. bit elé kerültek, kiesnek. Az így megüresedett bitek 0-kkal egészültek ki. Eddig nem túl bonyolult. Azonban figyeljünk oda, ha túl sokáig shiftelünk egy pozitív számot, akkor az utolsó érték, melyet fel tud venni a változónk (mielõtt még túlcsordulás miatt elérné a 0-t), az egy negatív szám!Nézzük mi történik negatív számok esetén: 11111111111111111111111111111000//-8<<211111111111111111111111111100000//-32[/quote]Láthatjuk hogy a balra shifteléskor a szám mindig 0-kkal egészítõdik ki, legyen az akár negatív, akár pozitív. Nem veszi figyelembe az MSB értékét, így ha túl sokat shiftelünk, akkor elõfordulhat, hogy a szám elveszti negatív értékét, és eléri a 0-t.1.4.6 Logikai biteltolás (logical shift, logikai shiftelés) operátorElõször is le kell tisztázni, hogy logikai shiftelésbõl csak egy létezik, jobbra logikai shiftelés(>>>). Sokban hasonlít a jobbra shiftelõ operátorra (>>), azonban van egy fontos különbség.Nézzük az elsõ példát: 00000000000000000000000000001000//8>>>200000000000000000000000000000010//2[/quote]Eddig nem igazán tapasztalhattunk semmi változást, minden pont ugyanúgy zajlott, mint a jobbra shiftelésnél(>>).Nézzük mi történik negatív szám esetén: 11111111111111111111111111111000//-8>>>200111111111111111111111111111110 //1073741822[/quote]Itt már láthatjuk a különbséget. Logikai shiftelés esetén az üres bitek helyére bekerülõ érték nem függ az MSB-tõl, hanem mindig 0. Emiatt soha nem kaphatunk eredményül negatív számot, logikai shiftelés esetén.1.5 Elsõ fejezet végeNos, ezennel átvettünk mindent, amit érdemes tudni a bitmûveletekrõl. Megmutattam az alapok, hogy hogyan épülnek fel a változók, valamint átvettük a fõ operátorokat. Eddig csak színtiszta elmélet volt. A következõ részben megmutatom, hogyan hasznosíthatjuk mindezt a gyakorlatban, vagyis mire is jó az a sok marhaság, amit eddig leírtam.2. Gyakorlati példák, érdekességek 2.1 Áttekintés, mikrõl is lesz még szóAz alábbiakban még pár dolgot szeretnék mutatni. Elõször is néhány \"függvénnyel\" fogok foglalkozni, majd mutatok még egy-két érdekességet. Konkrétan megmutatom, hogyan tudjuk egy jármû minden \"paneljének\" adatát kiíratni, részletesen(erre alapból nincs lehetõség, és a függvények is mind \"encode\" függvények, én viszont mutatok egy-egy decode \"verziót\" is.)Az alábbi 4 függvénnyel fogok foglalkozni: encode_tiresencode_panelsencode_doorsencode_lightsDe elõbb egy kis háttérinfó:A Pawnban a forrásban megadhatunk bináris számokat ugyanúgy, ahogyan Hex számokat is megadhatunk. Ehhez a \"0b\" prefixet kell használnunk. Tehát ha bináris számunk az 1011 és ezt akarjuk adni értékül egy változónak, azt így tehetjük meg: new var = 0b1011; 2.2 encode_tiresHáttér információ:A SA:MP-ban egy jármû abroncsainak adataid 4 bit tárolja. Az elsõ bit a jobb hátsó, a második bit a jobb elsõ, a harmadik bit a bal hátsó, a negyedik bit pedig a bal elsõ abroncsról tárol információt. Mivel 4 bitben eltárolhatóak az információk, így decimális formában 0-15 értékekkel is be lehet állítani a jármûvek abroncsait. 16-tól kezdve az értékek már ismétlõdni kezdenek, szóval a 16-nál az eredmény ugyanaz mint 0-nál, 17-nél ugyanaz mind 1-nél, és így tovább. Ez érthetõ, ha tudjuk hogyan épül fel a kettes számrendszer.(Ne felejtsük el, a biteket hátulról számozzuk!)Nos nézzük hogyan néz ki a kód: encode_tires(tire1, tire2, tire3, tire4){ return tire1 | tire2 << 1 | tire3 << 2 | tire4 << 3;} Láthatjuk, hogy bekérjük a 4 abroncs adatát(vagyis hogy ép[0], vagy kidurrant[1]), majd egy sima bitenkénti VAGY mûvelet és biteltolás kombinálásával visszatérünk az eredménnyel. A kódban a tire1 felel meg a jobb hátsó abroncsnak, a tire2 a jobb elsõ, a tire3 a bal hátsó, és a tire4 a bal elsõnek. Nézzünk egy konkrét példát. Tegyük fel, hogy én a hátsó 2 abroncsot ki akarom durrantani, még az elsõ kettõt épre akarom beállítani. Ez esetben így néz ki a dolog: encode_tire(1,0,1,0); Ha kiíratnánk konzolba a visszatérési értéket (%d-vel), 5-öt kapnánk. Miért is? Az 5 bináris alakja 0101. Hoppá, ha megnézzük ez pont fordítottja annak, amit mi megadtunk számjegyekként, csak már konkrét bináris számként. Nézzük hogyan is történhetett mindez, mit is csináltunk. A függvény a következõ mûveletet végzi: tire1 | tire2 << 1 | tire3 << 2 | tire4 << 3 Konyhanyelven úgy magyarázhatnánk el a dolgot, hogy ez a kód egymás mellé pakolgatja a megadott számokat, fordított sorrendben. Na de mi most nem a konyhanyelv miatt vagyunk ebben a leírásban, szóval nézzük a kódunkat. Lényegében ez futott le, a konkrét értékeinkkel nézve: 1 | 0 << 1 | 1 << 2 | 0 << 3 Bármily furcsa is, ez egy mûvelet, melyen szépen végig halad sorban a program. Elõször is írjuk át az egészet bináris alakba(az egyszerûség kedvéért most csak 4 bitet fogok kiírni): 0001 | 0000 << 1 | 0001 << 2 | 0000 << 3 Na erre se mondtuk volna rá a tutorial elsõ részének elolvasása elõtt, hogy ez egy mûveletsor. A rendszer elsõnek az eltolásokat végzi el (olyan ez mint a 2*3+2*4+2*5 mûveletben elõször a szorzásokat végezzük el, és csak utána adunk össze, ha nincs zárójel), és csak utána a bitenkénti VAGY mûveleteket, ezért mi is így fogunk tenni. Emlékezzünk, eltoláskor az \"üres\" bitek 0 értéket fognak felvenni. Végezzük el külön csak az eltolásokat: 0000 << 1 = 00000001 << 2 = 01000000 << 3 0000 Nos, ha ezeket elvégeztük, akkor nézzük, hogy is áll most a kódunk: 0001 | 0000 | 0100 | 0000 Négy azonos mûvelet van hátra, így hát a program innen más szépen sorban fog haladni. Tegyünk mi is így. Elsõ tehát a 0001 | 0000 mûvelet elvégzése. Emlékeztetõül, a bitenkénti VAGY mûvelet akkor ad 0-t eredményül, ha mind a kettõ érték 0, minden más esetben 1-et. Végezzük el külön az elsõ bitenkénti VAGY mûveletet: 0001 | 0000 = 0001 Nem volt bonyolult, nézzük tehát hogy áll akkor most a teljes mûveletünk: 0001 | 0100 | 0000 Haladunk szépen tovább, elvégezzük a 0001 | 0100 mûveletet. 0001 | 0100 = 0101 Nincs mit magyarázni rajta, egyszerû bitenkénti VAGY mûvelet volt ez is. Már csak ez van hátra: 0101 | 0000 Nos, ez sem lesz egy bonyolult mûvelet: 0101 | 0000 = 0101 Ezzel meg is kaptuk a végeredményt, ami nem más, mint a 0101 (tízes számrendszerben 5). Mivel azonban eléggé kusza lett így a számításunk, nézzük egyben hogyan is néz ki az egész, ha nem bontom ennyire részekre: 0100 | 0000 << 1 | 0001 << 2 | 0000 << 3v0001 | 0000 | 0100 | 0000v0001 | 0100 | 0000v0101 | 0000v0101 Nos, ezennel láthattuk a gyakorlatban is, hogyan mûködik a függvény. Kijelenthetjük tehát, hogy a függvény a számokat a sorrendjüknek megfelelõ számú bitbe helyezi. Mivel azonban a biteket hátulról számozzuk, így az 1,0,1,0 bemenetek végeredménye 0101 lesz. Így már nem is olyan bonyolult Na, ha már ilyen sokáig eljutottunk, akkor felhasználhatjuk ezt a kódot arra, hogy az alapjait figyelembe véve elkészítsük a decode változatot. Mivel 4 értékkel térünk vissza, ezért paraméterként kell majd 4 változó, melyeknek módosíthatjuk az értékét, illetve 1 paraméter pedig a dekódolandó szám. A függvényünk \"címsora\" tehát így néz ki: decode_tires(tires, &tire1, &tire2, &tire3, &tire4) Hogy példánk ne csak elmélet legyen, a tires változó legyen az elõbbi 0101 értékkel egyenlõ. Na akkor menjünk tovább. Bemeneti értékünk ugye a \"tires\" változóban lesz eltárolva, így ezzel fogjuk a mûveleteket végezni. Kezdjünk el gondolkozni. Hogyan is tudnánk ellenõrizni egy adatot. Legegyszerûbb ha a bitenkénti ÉS mûveletet alkalmazzuk. Miért is? Nos, a bitenkénti ÉS mûvelet ugye akkor ad eredményül 1-et, ha mind a 2 érték egy. Tehát ha min egy számot összehasonlítunk a 1-el(binárisan ugye 00000000000000000000000000000001, akkor az eredményünk lényegében arról fog tájékoztatni, hogy az elsõ bit 0, vagy 1. Nem is olyan bonyolult ez. Nézzük akkor az elsõ bitet. Arra emlékezhetünk még, hogy az elsõ bit a tire1, vagyis jobb hátsó abroncs adatait tárolja. Tehát: tire1 = tires & 1; Vagy konkrétan a példánk esetében: tire1 = 0101 & 0001 Mivel a bitenkénti ÉS mûvelet akkor ad 1-et eredményül, ha mind a kettõ bit 1, akkor már láthatjuk is, hogy miért elegendõ az elsõ bitet szemügyre venni. Mivel mind a 2 helyen az elsõ bit 1, ezért a tire1 változónk értéke is 1(0001) lesz. Egyszerû és nagyszerû, máris megkaptuk a jobb hátsó abroncs állapotát.Gondolkozzunk hát tovább. Nekünk most arra van szükségünk, hogy a 2. bit értékét megkapjuk. Hogyan kaphatjuk ezt meg? Legegyszerûbb megoldás, ha a tires változót jobbra shifteljük 1-el, majd ismételten bitenkénti ÉS mûveletet végzünk. Nézzük tehát, hogyan is nézne ez ki: tire2= tires >> 1 & 1; Vagy konkrétan a mi példánkban: tire2 = 0101 >> 1 & 0001vtire2 = 0010 & 0001 Ismételten bitenkénti ÉS mûvelet, és az eredményünk 0, mivel csak az egyik bit 1, a másik 0, így a bitenkénti ÉS mûvelet eredménye is 0. Ezen az elven megcsináljuk a maradék 2 abronccsal is: tire3= tires >> 2 & 1;tire4= tires >> 3 & 1; Vagy a mi esetünkben konkrét példánkkal: tire3 = 0101 >> 2 & 0001 // azaz: --> 0001 & 0001 = 1tire4 = 0101 >> 3 & 0001 // azaz: --> 0000 & 0001 = 0 A teljes kódunk tehát: decode_tires(tires, &tire1, &tire2, &tire3, &tire4){ tire1 = tires & 1; tire2 = tires >> 1 & 1; tire3 = tires >> 2 & 1; tire4 = tires >> 3 & 1;} A konkrét példánkban a 4 változónk értéke a függvény lefutása után: tire1: 1tire2: 0tire3: 1tire4: 0 Ha összehasonlítjuk, ezek pont azok az értékek, mint amiket az encode függvényben megadtunk, tehát úgy tûnik jól dolgoztunk 2.3 encode_lightsEz a rész lesz a legrövidebb. Az egész lámpás rész pontosan ugyanarra a sémára épül mint az abroncsok. Az adott bitek ugyanazon a \"helyen\" lévõ lámpa adatait szolgáltatják, ugyanúgy 0 az ép, és 1 a sérült lámpák állapot azonosítója, és ugyanúgy 1-1 bit tárolja az értékeket.Maga a kód: encode_lights(light1, light2, light3, light4){ return light1 | light2 << 1 | light3 << 2 | light4 << 3;} Láthatjuk hogy halál pontosan ugyanaz mint az encode_tires, csak más változónevekkel. Így hát a decode függvény is ugyan az lesz, csak a változóneveket kell módosítanunk: decode_lights(lights, &light1, &light2, &light3, &light4){ light1 = lights & 1; light2 = lights >> 1 & 1; light3 = lights >> 2 & 1; light4 = lights >> 3 & 1;} Ha megnézzük látjuk, hogy teljesen ugyan az a séma, ugyan az a felépítés, minden megegyezik, ezért nem is írtam le újból ugyanazt, amit egyszer már kifejtettem.2.4 encode_panelsHáttér információ:A SA:MP 7 panelrõl tárol adatokat. Egy panel adatait 4 bit írja le. Nos, ennél több infóra nekünk most nincs szükségünk, az elõzõ kód alapján ezen kód is megérthetõ, így az encode verzióhoz nem is nagyon szeretnék magyarázatot fûzni. A különbség az encode_tires-hez képest, hogy itt 4-essével shiftelünk, mivel 1 panel adatait 4 bit tárolja.Maga a kód: encode_panels(flp, frp, rlp, rrp, windshield, front_bumper, rear_bumper){ return flp | frp << 4 | rlp << 8 | rrp << 12 | windshield << 16 | front_bumper << 20 | rear_bumper << 24;} Nézzünk egy decode verziót, ehhez a kódhoz. Alapjaiban ugyanazon a vonalon kell elindulnunk, mint a decode_tires esetében. Itt most 7 visszatérési értékünk lesz, és 1 bemeneti érték. Tehát így néz ki a függvény header: decode_panels(panels, &flp, &frp, &rlp, &rrp, &windshield, &front_bumper, &rear_bumper) A folytatásban megint csak az elõbbi verziót kell alapul vennünk, azonban itt lesz egy kis különbség. Egyértelmû, hogy mivel 1 panel adatait 4 bit tárolja, így a 4 bitet nekünk egyben kell lekérni, és egyben kell vele visszatérnünk. Megint csak a bitenkénti ÉS mûveletet fogjuk alkalmazni, azonban mivel 4 bitrõl van szó, így egy olyan számmal kell összehasonlítanunk, mely bináris alakban 1111. Ez a szám nem más, mint a 15. Hogy miért ez kell?Ha egy bitsorozat tartalmát ki szeretnénk másolni egy másik változóba, akkor a bitenkénti ÉS mûveletre vna szükségünk. A bitenkénti ÉS mûvelet akkor ad 1-et eredményül, ha mindkét bit 1, ha valamelyik 0, akkor a visszatérés már 0 lesz. Ergó tehát, ha egy csupa 1-esekbõl álló bitsorozattal vetjük össze az eredeti bitsorozatunkat, akkor lényegében lemásoljuk a sorozatot, egy másik változóba, hisz eredményül mindig az eredeti sozatot fogjuk visszakapni. Íme néhány példa: 0101 & 1111 = 01011111 & 1111 = 11110000 & 1111 = 00001011 & 1111 = 1011 Láthatjuk tehát, hogy ha csupa 1-est tartalmazó bitsorozattal hasonlítjuk össze az eredeti számunkat, akkor lényegében egy másolatot készítünk. Mivel nekünk erre a 4 bitre van szükségünk a 32-bõl, így pontosan ezt kell tennünk, ezen 4 bitet lemásolni egy másik változóba, hogy késõbb ezt majd egy másik függvénnyel még jobban darabokra szedhessük.Íme tehát akkor a kódunk elsõ sora: flp = panels & 15; Mivel 4-esével vizsgáljuk a biteket, így lényegében annyit kell még változtatnunk az elõzõ kódon(decode_tires), hogy az 1-esével növekvõ shiftelések helyett 4-esével shifteljünk. Azaz a 2. panel adatainak lekérdezése: frp = panels >> 4 & 15; Innen pedig már csak mindig további 4 shiftelést el kell végeznünk, és szépen végig haladhatunk a panels változó bitjein, és megkaphatjuk minden panel adatait külön-külön. rlp = panels >> 8 & 15;rrp = panels >> 12 & 15;windshield = panels >> 16 & 15;front_bumper= panels >> 20 & 15;rear_bumper = panels >> 24 & 15; Ezennel minden panel adatait megkaptuk. Ezután már akár a decode_tires függvényt felhasználva szét lehet szedni az adatokat darabokra, de akár egy saját névvel, és más változó nevekkel ellátott másik függvényt is létre lehet hozni a decode_tires mintájára.Tehát az egész kód egyben: decode_panels(panels, &flp, &frp, &rlp, &rrp, &windshield, &front_bumper, &rear_bumper){ flp = panels & 15; frp = panels >> 4 & 15; rlp = panels >> 8 & 15; rrp = panels >> 12 & 15; windshield = panels >> 16 & 15; front_bumper= panels >> 20 & 15; rear_bumper = panels >> 24 & 15;} Igazából a függvény ugyanarra a sémára épül mint a decode_tires, csak annyi a különbség, hogy 4 bitenként kapjuk az adatokat, nem pedig egyesével, mivel itt minden panelnek 4 adatát tudjuk majd még lekérdezni. Nem tudom lehet-e még ehhez a részhez hozzáfûzni valamit, szóval szerintem ugorjunk. 2.5 encode_doorsHáttér információ:A SA:MP képes minden jármû 4 \"ajtajának\" állapotát tárolni. Ezek az \"ajtók\"(Doors-t hogy fordíthatnám jobban?) konkrétan a motorháztetõ, csomagtartó, vezetõoldali ajtó, és anyósülés oldali ajtó. A 2 hátsó ajtó állapotát jelenleg még nincs lehetõségünk lekérdezni.Amikor lekérdezzük egy jármû \"ajtajának\" adatait, azt a SA:MP 4 bájtként(32 bit) adja vissza. Ebbõl a 4 bájtból 1 bájt(8 bit) jut minden egyes objektumra. Vagyis minden egyes objektum adatait 8 bit tárolja. Vagyis tárolhatná...A SA:MP csak az elsõ 3 bitet használja, a további bitek üresek lesznek, nincs funkciójuk, talán majd késõbb lesz. Az elsõ bit azt tárolja, hogy az objektum zárt[0], vagy nyitott[1](nem az jelenti, hogy bezárt, hanem becsukott/kinyitott) állapotban van-e. A második bit azt adja meg, hogy az objektum ép[0] vagy sérült[1]. A 3. bit arról tájékoztat, hogy az adott objektum rajta van-e még a jármûvön[0] vagy már leesett[1]. A többi bit mint mondtam üres. Ez majd a decode függvénynél lesz fontos.Nézzük tehát a konkrét kódot: encode_doors(bonnet, boot, driver_door, passenger_door, behind_driver_door, behind_passenger_door){ #pragma unused behind_driver_door #pragma unused behind_passenger_door return bonnet | boot << 8 | driver_door << 16 | passenger_door << 24;} A 2 hátsó ajtóval nem foglalkozunk, mivel azok jelenleg még nem támogatottak ugyebár, de a kód készítõje gondolt a jövõre is, vagy fogalmam sincs miért adta meg ezt a két paramétert is, hisz ígyis-úgyis módosítani kell a kódon... Ha behoznak még 2 ajtót, akkor az 1 bájtos tárolással már 6 bájt lenne, de 1 változó maximum 4 bájt lehet, tehát a kódot úgyis módosítani kell, ha meg arra gondoltak, hogy legalább a paramétereket nem kell majd szegény scriptereknek minden sorban átírni, az megint hülyeség, hisz ki adna értéket olyan paraméternek, amely nincs használva?...Nos mindegy, nem a kritizálásért vagyunk most itt. Ha megnézzük, a kód 8-as shiftelést használ, mivel egy objektum adatait 8 bit tárolja. A mûködési elv ugyanaz, mint az elõzõ encode függvényeknél, így erre nem kívánok megint kitérni.Nézzük azonban hogyan írnánk ehhez egy decode függvényt. Mikor megkérdeztem errõl ismerõsömet, õ azt mondta, ugyanúgy mint az elõzõ, decode_panels függvénynél, csak 8-as shiftelést használunk, és 15 helyett 255-el végezzük a bitenkénti ÉS mûveletet. 255-el? minek?No erre is ki fogok térni késõbb, de haladjunk a szokásos módon. 1 bemeneti paraméterünk van, és 4 kimeneti, vagyis a header rész a következõ: decode_doors(doors, &bonnet, &boot, &driver_door, &passenger_door) Ezzel meg is vagyunk, itt nem látok különösebb magyarázni valót. Én most nem fogok törõdni a 2 nem létezõ ajtóval, ha egy jövõbeni verzióban netán azokat is behoznák mint lekérdezhetõ objektum, akkor ígyis-úgyis módosítani kell majd a kódot, addig meg úgysincs rá szükségünk.Nos, mint az eddigi decode függvényeknél, elsõ feladatunk egy shiftelés mentes bitenkénti ÉS mûvelet elvégzése. Az egyik megoldás a következõ: bonnet = doors & 255; Lehet így is. Azonban véleményem szerint felesleges itt egy ekkora számmal \"szenvednünk\". Miért használjunk ekkora számot, és ezáltal bonyolítsuk feleslegesen a kódunkat, ha nekünk a 8 bitbõl csak az elsõ 3 lesz ami adatot tárol? Bõven elegendõ egy olyan szám, amely bináris alakja 111. Ez nem más, mint a 7. Tehát a következõ megoldás ugyanúgy helyes: bonnet = doors & 7; A két kód pontosan ugyanazt az eredményt fogja visszaadni, semmi különbség a kettõ között. Csupán elsõ esetében nagy számmal dolgozunk, még második verzió esetén csak akkorával, amekkora szükséges.A kód többi részében csak 8-as shifteléssel végigcammogunk a doors változó többi bitjén is: boot = doors >> 8 & 7;driver_door = doors >> 16 & 7;passenger_door = doors >> 24 & 7; A teljes kód egyben: decode_doors(doors, &bonnet, &boot, &driver_door, &passenger_door){ bonnet = doors & 7; boot = doors >> 8 & 7; driver_door = doors >> 16 & 7; passenger_door = doors >> 24 & 7;} A kód pont ugyanazon az elven épül fel, mint az összes többi decode függvényünk, csak épp a benne használatos értékek az \"ajtó\" objectumok adatstruktúrájához vannak igazítva.2.6 Egyéb ötletek gyakorlati alkalmazásra, hasznosításraJoggal felmerülhet a kérdés, hogy ennyi? Emiatt rágtuk végig magunkat ezen néhány tízezer karakteren? Nem. Nem ennyi. A SA:MP-ban rengeteg felhasználási módja lehet még a bitmûveleteknek. Mutatok is néhány dolgot, mire jók még.2.6.1 RGBA->HEX; HEX->RGBA konvertálásokNos, bármily \"hihetetlen\", erre is használható. Miért is? Nos, 16-os számrendszerben 0-9, valamint A-F \"szánjegyek\" léteznek. A hexadecimális számjegy decimális értékben 0-15 lehet. Ha megnézzük bináris értékeket, 0-15 az 4 bittel leírható, hiszen a 15 az bináris alakban 1111. De ha józan paraszti ésszel belegondolunk is. 16 az 24. Így tehát 1 hexadecimális érték, az 4 bináris értékkel írható le. Ebbõl már sejthetõ, hogy itt is shiftelésre lesz szükségünk. Naná Mivel 4 bit ír le egy hexadecimális számjegyet, így 4-esével kell majd shiftelnünk.Mi is az az RGBA?RGBA: A színek olyan meghatározási módja, melyben a 3 alapszín([r]ed, [g]reen, lue) mellett egy 4. érték, az úgynevezett [a]lpha is szerepel, amely az áttetszõséget adja meg. A legtöbb program(pl. paint, PS), és pl. a css(külön függvénnyel!) is elfogadja ezen értékeket decimális számokként(0-255) is, a SA:MP-ban azonban hexadecimális alakban kell megadnunk. Bármily furcsa a következõ 4 sor pont ugyan azt jelenti:\"Normál\" RGB alak(pl. paint, photoshop): Piros: 0, Zöld: 255, Kék: 255CSS alak(függvénnyel): rgba(0, 255, 255, 1);Pawn alak(1): 0x00FFFFFFPawn alak(2): 16777215 Ó igen, a Pawn-ban decimális számokként is megadhatjuk a színeket, hisz a program mindent bináris számrendszerbe fog átszámolni. De akár bináris alakban is megadhattuk volna:Pawn alak(3): 0b111111111111111111111111 Az, hogy mi a Pawn-nak hexadecimális alakban szoktuk megadni, annak az egyetlen oka, hogy így a leginkább \"átlátható\", és ez egy amúgy is elfogadott módja (mármint a hexadecimális számmal való megadás) a színek leírásának a számítástechnikában.Nos, mivel 1 szín 0 és 255 közötti értéket tartalmaz a piros, zöld és kék színekbõl, ez hexadecimálisan 2 számjeggyel leírható: 00-FF. Az FF értéke 255, aki nem hiszi, számoljon utána (162-1)2 hexadecimális számjegy az pedig 8 bináris számjeggyel leírható(00000000-11111111). Így már láthatjuk, hogy 8-as shiftelésre lesz szükségünk. Másra nincs is szükségünk, az RGBA->HEX konverter elkészítéséhez.stock RGBAToHex( r, g, b, a ){return a | b << 8 | g << 16 | r << 24;} Az, hogy miért fordított sorrendben kellett megadnunk az értékeket, az megint csak azzal magyarázható, hogy a biteket hétköznapi értelemben véve visszafelé számozzuk.Egy ellentét, azaz HexToRGBA függvény megírása sem túl bonyolult, ugyan ezt kell csinálnunk, csak visszafelé.stock HexToRGBA( colour, &r, &g, &b, &a ){a = colour & 0xFF;b = (colour >> 8 ) & 0xFF;g = (colour >> 16 ) & 0xFF;r = (colour >> 24 ) & 0xFF;} A 0xFF-et természetesen decimális értékkel is megadhatjuk(255).No akkor lehet ugye RGBA-t ARGB-be is konvertálni, az így néz ki:RGBAToARGBHEX(r,g,b,a){return (r & 0xff) << 24 | (g & 0xff) << 16 | (b & 0xff) << 8 | a & 0xff;} Ez máris egy újabb felhasználási módja a bitmûveleteknek. De, mutatok még 2.6.2 Boolen változók helyett IntegerAz al-al-alcímet elolvasva joggal felmerülhet a kérdés, hogy ez meg hogy jön ide. Nos ez úgy jön ide, hogy most egy kis memóriatakarékosságról fogok itt regélni. Adhattam volna azt is címnek, hogy miért ne használjunk boolean változókat, ha legalább 5 igaz/hamis értéket el akarunk tárolni.Nos akkor egy kis memórialecke. Azt már tisztáztuk, hogy az integer változók 32 bitesek, vagyis 4 bájtot foglalnak le a memóriában maguknak. A boolean típusú változókról azonban még nem volt szó. Mivel 1 értéket tárolnak csak, az is csak igaz vagy hamis lehet, így joggal gondolhatnánk, hogy 1 bit a mérete. Azonban nem...1 boolean változó 8 bitet, azaz 1 bájtot foglal le magának a memóriában. Hogy ez miért így van, az a számítógépek memóriahasználatával magyarázható. Manapság nincs mód arra, hogy a memóriában egyetlen bitet módosítsunk, csak 8-as \"csomagokban\", vagyis bájtonként tudjuk ezt megtenni. Hogy ez miért van így, azt most nem fogom itt fejtegetni Nos, ha megnézzük tehát, a boolean változó egy nagyon-nagyon pazarló megoldás. Hiszen 1 darab boolean változó 1 bájtot foglal, 4 darab boolean változó esetén ez már 4 bájt, ami megegyezik 1 integer változó méretével. Azonban, még a 4 boolean változóban 4 igaz/hamis érték kerülhet tárolásra, addig egy integerben 32 igaz/hamis értéket is eltárolhatunk.Bizony, 32 igaz/hamis érték. Hiszen 1 integer változó 32 bitbõl áll, ha minden bitet hasznosítunk, akkor a bitmûveletek felhasználásával 32 igaz/hamis értéket is eltárolhatunk. Összehasonlítva:32 darab boolean változó mérete 32 bájt.1 integer változó mérete: 4 bájt. Vagyis 32 boolean változó esetén nyolcad annyi memóriánkba kerül, ha egy darab integerben tároljuk el az értékeket. Persze 32 booleanra ritkán van szükség, de egy RP modban simán elõfordulhat. Gondoljunk csak bele... Van 32 boolean-tömbös változónk, ez a maximum slotszámot(1000) figyelembe véve: 32*1000 byte azaz 32000 byte ami ~31 kilobyte. Ha ezt egyetlen integer-tömbbel oldanánk meg: 4*1000 byte, azaz 4000 byte, ami ~3,9 kilobyte. Azért így nézve már nem annyira \"elhanyagolandó\" az a különbség szerintem =)Akár írhatnánk erre külön egy függvényt, mely az adott bit értékével tér vissza. Nem is bonyolult. Így már azok számára is használhatóak a bitmûveletek, akik nem értenek hozzá, és rengeteg memóriát takaríthatnak meg.Íme egy függvény az adatok kiolvasására:stock GetData(integer, bit){ return integer >> bit & 1; //vagy az 1 helyett akár 0x01 alakban is megadhatjuk, nincs jelentõsége} És a párja, adat írásra:stock SetData(&integer, bit, value){ integer |= value << bit; return 1;} Persze ezt még lehetne csinosítani, hogy pl. ne 0-tól hanem 1-tõl számoljuk a biteket, vagy hogy 31(vagy 32 ha 1-tõl számolunk) felett 0 legyen a return értéke, esetleg a value-re is rakhatnánk ellenõrzést, hogy 0-1-e az értéke, vagy más, de ilyenekkel most nem törõdtem.2.6.3 Több érték, egy integerbeNa ez az a rész, ami véleményem szerint már csak a nagyon megrögzött memóriafelszabadítók szerint nem lesz felesleges Amennyiben nincs szükségünk túl nagy számokra(pl. csak 0-15, vagy akár csak 0-7, de akár még a 0-255 is megfelel), akkor akár egy integerbe is tehetjük õket. Hogyan? Egyszerû. Csoportosítjuk a biteket. Tegyük fel, hogy maradván egy RP szervernél, el akarjuk tárolni ûbergigantikusan memóriakímélõ megoldással a játékos házának számát(ez legyen mondjuk 0-100 közötti szám), a játékos frakcióját(ez legyen 0-15 között), a játékos rangját(0-7 között), hogy a telefonja be van-e kapcsolva(0-1), a játékos szolgálati jármûvének id-jét(legyen 0-255 között), és az alapmunkáját(0-15 között). Ez is megoldható, egyetlen integerben, és ismételten 1000 slottal számolva máris megspóroltunk 4*4*1000+1*1000 byte-t(5 integer, és 1 boolean helyett csupán 1 integert használunk), ami ~16,6 kilobyte. Azért nem mindegy az, hogy 20,5 kb-ot vagy 4 kb-ot használunk szerintem... Ezt a következõ módszerrel valósíthatnánk meg:#define DATA_HOUSE 0#define DATA_FACTION 1#define DATA_RANK 2#define DATA_PHONESTATE 3#define DATA_DUTYCAR 4#define DATA_JOB 5stock GetData(playerinfo, data){ switch(data) { case DATA_HOUSE: return playerinfo & 127;//7 bit case DATA_FACTION: return playerinfo >> 7 & 15;//4 bit case DATA_RANK: return playerinfo >> 11 & 7;//3 bit case DATA_PHONESTATE: return playerinfo >> 14 & 1;//1 bit case DATA_DUTYCAR: return playerinfo >> 15 & 255;//8 bit case DATA_JOB: return playerinfo >> 23 & 15;//4 bit}return -1;}stock SetData(&playerinfo, data, value){ switch(data) { case DATA_HOUSE: { playerinfo = playerinfo & ~127 | value; }//7 bit (0-127 közötti maximálisan) case DATA_FACTION: { playerinfo = playerinfo & ~(15 << 7) | value << 7; }//4 bit (0-15 között) case DATA_RANK: { playerinfo = playerinfo & ~(7 << 11) | value << 11; }//3 bit (0-7 között) case DATA_PHONESTATE: { playerinfo = playerinfo & ~(1 << 14) | value << 14; }//1 bit (0-1) case DATA_DUTYCAR: { playerinfo = playerinfo & ~(255 << 15) | value << 15; }//8 bit (0-255 között) case DATA_JOB: { playerinfo = playerinfo & ~(15 << 23) | value << 23; }//4 bit (0-15 között) } return -1;}stock SetDatas(&playerinfo, house, faction, rank, phonestate, dutycar, job){playerinfo |= house | (faction << 7) | (rank << 11) | (phonestate << 14) | (dutycar << 15) | (job << 23);} Ez már azért elég morbid, ezt inkább csak érdekességképp mutattam, de éppenséggel ez is használható lenne, csak ehhez már kell egy kis õrültség 2.7 Második fejezet végeNos, láthattuk hogyan is mûködik mindazon sok \"marhaság\", amit az elsõ fejezetben elméletként átvettünk. Láthattuk hogyan mûködnek a jármûvek paneljének adataival babráló encode függvények, valamint megmutattam, hogyan írhatunk hozzájuk decode verziókat. Láthattunk még példát egyéb gyakorlati hasznosításra, kicsit megkoccoltuk a hexadecimális számok világát is, majd szóba került egy kis memóriatakarékosság. Végül mutattam egy összetettebb, elvetemült példát is, a bitmûveletek hasznosítására.3. Összegzés, végszó Úgy gondolom, hogy elég részletesen körbe jártuk a témát. Megnéztük a bitmûveletek elméleti, és gyakorlati oldalát is, láthattunk gyakorlati példákat. Nem kell elsõre megérteni ezt az egészet, sõt... Elegendõ ha egyszer elolvassuk, emésztjük kicsit, majd késõbb megint elõvesszük, akkor netán már kicsit többet is megértünk belõle. Nem egy kihagyhatatlan része a Pawno-nak, de azért van annak haszna, ha az ember jártas ebben a témában is, bár sokak szerint nem éri meg a sok fáradtságot, és idõt, mely a megértéséhez, begyakorlásához, használatának elsajátításához szükséges. Próbáltam mindent részletesen kifejteni. A bonyolultabb dolgokhoz sokat magyarázni, az egyszerûbbek esetén csak visszautalni az alapokhoz. Sok dolgot talán már túl is magyaráztam. Ezt a leírást nem 1 nap alatt készítettem el, kb. 1 hónapja írtam meg az elméleti oldalát, most az elmúlt pár napban pedig a gyakorlatot. Esténként 1-2 órát szántam rá csupán, és mivel ilyenkor már nem vagyok épp a toppon, elképzelhetõ, hogy néhány helyen helyesírási, vagy elvi hibát vétettem. Amennyiben valaki ilyet találna, kérem szóljon, és korrigálni fogom. Nos, azt hiszem nincs is más hátra, mint hogy sok szerencsét kívánjak a bitmûveletek elsajátításához, és megosszam a forrásokat. Remélem néhányan hasznosnak találják, eme igen hosszúra sikerült leírást.4. Források: Internet:WikipédiaSA:MP WikiSA:MP hivatalos fórumKönyv, e-book:Pohl László - A programozás alapjai(BME - Elektronikus Eszközök Tanszéke, Budapest, 2010)Siroki László - C programozás - Bitmûveletek jegyzeteˆ Anthony, 2012 - Minden jog fenntartva.A leírás egészben, vagy részleteiben szabadon terjeszthetõ, ameddig egyértelmûen fel van tüntetve az eredeti készítõ, valamint hogy honnan is származik. « Utoljára szerkesztve: 2012. augusztus 01. - 10:53:54 írta Anthony » Naplózva
A ~A0110[/quote]Példának nézzük meg, mi történik, ha a bitenkénti NEGÁLÁST alkalmazzuk a 7-es számra. A 7 bináris számrendszerben mint már írtam 00000000000000000000000000000111-nek felel meg. Tehát a ~7, az ugyaz mint ha ezt írnám: ~00000000000000000000000000000111. Nézzük tehát, mi történik: ~0000000000000000000000000000011111111111111111111111111111111000[/quote]Eredményül most 11111111111111111111111111111000-t kaptunk, ami a kettes számrendszerben a -8-nak felel meg. A következõ 2 sor tehát ugyanazt jelenti: Bin:~00000000000000000000000000000111=11111111111111111111111111111000Dec:~7=-8[/quote]Megint néhány példa: ~00000000000000000000000000000101 = 11111111111111111111111111111011~00000000000000000000000001110110= 11111111111111111111111110001010[/quote]Ehhez kapcsolódóan lenne itt egy fontos megjegyzendõ dolog. Aki nem érti, hogy miért -8 a megoldás, az nem olvasta el elég rendesen a \"Részletesebben a bitekrõl a Pawn-ban\" részt.1.4.5 Biteltolás (arithmetic shift, shiftelés) operátor(ok)A shift operátorok(<<, >>) a bitek tologatására használhatóak. A << balra tol, a >> jobbra. Bal oldalt található a szám, amit tologatni kívánunk, jobb oldalt pedig az, hogy mennyivel szeretnénk eltolni.1.4.5.1 Jobbra toló operátor(>>):Ez az operátor a szám bitjeit jobbra tolja a megadott mértékkel. Egy bittel való eltolás, 2-vel való osztásnak felel meg. Nézzünk egy példát: 00000000000000000000000000001000//8>>200000000000000000000000000000010//2[/quote]Láthatjuk, hogy semmi más nem történt, mint minden bitet 2-vel hátrébb mozgattunk. Az így \"megüresedett\" bitek helyére pedig 0-k kerültek. Lényegében az történt, hogy az utolsó 2 bit kiesett, az így megmaradt biteket 2-vel hátrébb csúsztattuk, majd az elsõ 2, így \"üres\" bit 0 értéket kapott.Nézzünk egy példát negatív számmal: 11111111111111111111111111110000//-8>>211111111111111111111111111111100//-2[/quote]Láthatjuk, hogy itt fontos szerepe van az MSB-nek. Ettõl függ ugyanis, hogy 0-k vagy 1-esek kerülnek a \"megüresedett\" bitek helyére. Fontos tehát megjegyezni, hogy a jobbra shiftelés megtartja a számok elõjelét, nem változtat azon!1.4.5.2 Balra toló operátor(<<):Ez az operátor a szám bitjeit balra tolja a megadott mértékkel. Egy bittel való eltolás, 2-vel való szorzásnak felel meg. Nézzünk egy példát: 00000000000000000000000000001000//8<<200000000000000000000000000100000//32[/quote]Itt megint könnyedén észrevehetõ mi is történt. Minden bitet fentebb \"toltunk\" 2-vel. Azok a bitek, amelyek ezáltal a 32. bit elé kerültek, kiesnek. Az így megüresedett bitek 0-kkal egészültek ki. Eddig nem túl bonyolult. Azonban figyeljünk oda, ha túl sokáig shiftelünk egy pozitív számot, akkor az utolsó érték, melyet fel tud venni a változónk (mielõtt még túlcsordulás miatt elérné a 0-t), az egy negatív szám!Nézzük mi történik negatív számok esetén: 11111111111111111111111111111000//-8<<211111111111111111111111111100000//-32[/quote]Láthatjuk hogy a balra shifteléskor a szám mindig 0-kkal egészítõdik ki, legyen az akár negatív, akár pozitív. Nem veszi figyelembe az MSB értékét, így ha túl sokat shiftelünk, akkor elõfordulhat, hogy a szám elveszti negatív értékét, és eléri a 0-t.1.4.6 Logikai biteltolás (logical shift, logikai shiftelés) operátorElõször is le kell tisztázni, hogy logikai shiftelésbõl csak egy létezik, jobbra logikai shiftelés(>>>). Sokban hasonlít a jobbra shiftelõ operátorra (>>), azonban van egy fontos különbség.Nézzük az elsõ példát: 00000000000000000000000000001000//8>>>200000000000000000000000000000010//2[/quote]Eddig nem igazán tapasztalhattunk semmi változást, minden pont ugyanúgy zajlott, mint a jobbra shiftelésnél(>>).Nézzük mi történik negatív szám esetén: 11111111111111111111111111111000//-8>>>200111111111111111111111111111110 //1073741822[/quote]Itt már láthatjuk a különbséget. Logikai shiftelés esetén az üres bitek helyére bekerülõ érték nem függ az MSB-tõl, hanem mindig 0. Emiatt soha nem kaphatunk eredményül negatív számot, logikai shiftelés esetén.1.5 Elsõ fejezet végeNos, ezennel átvettünk mindent, amit érdemes tudni a bitmûveletekrõl. Megmutattam az alapok, hogy hogyan épülnek fel a változók, valamint átvettük a fõ operátorokat. Eddig csak színtiszta elmélet volt. A következõ részben megmutatom, hogyan hasznosíthatjuk mindezt a gyakorlatban, vagyis mire is jó az a sok marhaság, amit eddig leírtam.2. Gyakorlati példák, érdekességek 2.1 Áttekintés, mikrõl is lesz még szóAz alábbiakban még pár dolgot szeretnék mutatni. Elõször is néhány \"függvénnyel\" fogok foglalkozni, majd mutatok még egy-két érdekességet. Konkrétan megmutatom, hogyan tudjuk egy jármû minden \"paneljének\" adatát kiíratni, részletesen(erre alapból nincs lehetõség, és a függvények is mind \"encode\" függvények, én viszont mutatok egy-egy decode \"verziót\" is.)Az alábbi 4 függvénnyel fogok foglalkozni: encode_tiresencode_panelsencode_doorsencode_lightsDe elõbb egy kis háttérinfó:A Pawnban a forrásban megadhatunk bináris számokat ugyanúgy, ahogyan Hex számokat is megadhatunk. Ehhez a \"0b\" prefixet kell használnunk. Tehát ha bináris számunk az 1011 és ezt akarjuk adni értékül egy változónak, azt így tehetjük meg: new var = 0b1011; 2.2 encode_tiresHáttér információ:A SA:MP-ban egy jármû abroncsainak adataid 4 bit tárolja. Az elsõ bit a jobb hátsó, a második bit a jobb elsõ, a harmadik bit a bal hátsó, a negyedik bit pedig a bal elsõ abroncsról tárol információt. Mivel 4 bitben eltárolhatóak az információk, így decimális formában 0-15 értékekkel is be lehet állítani a jármûvek abroncsait. 16-tól kezdve az értékek már ismétlõdni kezdenek, szóval a 16-nál az eredmény ugyanaz mint 0-nál, 17-nél ugyanaz mind 1-nél, és így tovább. Ez érthetõ, ha tudjuk hogyan épül fel a kettes számrendszer.(Ne felejtsük el, a biteket hátulról számozzuk!)Nos nézzük hogyan néz ki a kód: encode_tires(tire1, tire2, tire3, tire4){ return tire1 | tire2 << 1 | tire3 << 2 | tire4 << 3;} Láthatjuk, hogy bekérjük a 4 abroncs adatát(vagyis hogy ép[0], vagy kidurrant[1]), majd egy sima bitenkénti VAGY mûvelet és biteltolás kombinálásával visszatérünk az eredménnyel. A kódban a tire1 felel meg a jobb hátsó abroncsnak, a tire2 a jobb elsõ, a tire3 a bal hátsó, és a tire4 a bal elsõnek. Nézzünk egy konkrét példát. Tegyük fel, hogy én a hátsó 2 abroncsot ki akarom durrantani, még az elsõ kettõt épre akarom beállítani. Ez esetben így néz ki a dolog: encode_tire(1,0,1,0); Ha kiíratnánk konzolba a visszatérési értéket (%d-vel), 5-öt kapnánk. Miért is? Az 5 bináris alakja 0101. Hoppá, ha megnézzük ez pont fordítottja annak, amit mi megadtunk számjegyekként, csak már konkrét bináris számként. Nézzük hogyan is történhetett mindez, mit is csináltunk. A függvény a következõ mûveletet végzi: tire1 | tire2 << 1 | tire3 << 2 | tire4 << 3 Konyhanyelven úgy magyarázhatnánk el a dolgot, hogy ez a kód egymás mellé pakolgatja a megadott számokat, fordított sorrendben. Na de mi most nem a konyhanyelv miatt vagyunk ebben a leírásban, szóval nézzük a kódunkat. Lényegében ez futott le, a konkrét értékeinkkel nézve: 1 | 0 << 1 | 1 << 2 | 0 << 3 Bármily furcsa is, ez egy mûvelet, melyen szépen végig halad sorban a program. Elõször is írjuk át az egészet bináris alakba(az egyszerûség kedvéért most csak 4 bitet fogok kiírni): 0001 | 0000 << 1 | 0001 << 2 | 0000 << 3 Na erre se mondtuk volna rá a tutorial elsõ részének elolvasása elõtt, hogy ez egy mûveletsor. A rendszer elsõnek az eltolásokat végzi el (olyan ez mint a 2*3+2*4+2*5 mûveletben elõször a szorzásokat végezzük el, és csak utána adunk össze, ha nincs zárójel), és csak utána a bitenkénti VAGY mûveleteket, ezért mi is így fogunk tenni. Emlékezzünk, eltoláskor az \"üres\" bitek 0 értéket fognak felvenni. Végezzük el külön csak az eltolásokat: 0000 << 1 = 00000001 << 2 = 01000000 << 3 0000 Nos, ha ezeket elvégeztük, akkor nézzük, hogy is áll most a kódunk: 0001 | 0000 | 0100 | 0000 Négy azonos mûvelet van hátra, így hát a program innen más szépen sorban fog haladni. Tegyünk mi is így. Elsõ tehát a 0001 | 0000 mûvelet elvégzése. Emlékeztetõül, a bitenkénti VAGY mûvelet akkor ad 0-t eredményül, ha mind a kettõ érték 0, minden más esetben 1-et. Végezzük el külön az elsõ bitenkénti VAGY mûveletet: 0001 | 0000 = 0001 Nem volt bonyolult, nézzük tehát hogy áll akkor most a teljes mûveletünk: 0001 | 0100 | 0000 Haladunk szépen tovább, elvégezzük a 0001 | 0100 mûveletet. 0001 | 0100 = 0101 Nincs mit magyarázni rajta, egyszerû bitenkénti VAGY mûvelet volt ez is. Már csak ez van hátra: 0101 | 0000 Nos, ez sem lesz egy bonyolult mûvelet: 0101 | 0000 = 0101 Ezzel meg is kaptuk a végeredményt, ami nem más, mint a 0101 (tízes számrendszerben 5). Mivel azonban eléggé kusza lett így a számításunk, nézzük egyben hogyan is néz ki az egész, ha nem bontom ennyire részekre: 0100 | 0000 << 1 | 0001 << 2 | 0000 << 3v0001 | 0000 | 0100 | 0000v0001 | 0100 | 0000v0101 | 0000v0101 Nos, ezennel láthattuk a gyakorlatban is, hogyan mûködik a függvény. Kijelenthetjük tehát, hogy a függvény a számokat a sorrendjüknek megfelelõ számú bitbe helyezi. Mivel azonban a biteket hátulról számozzuk, így az 1,0,1,0 bemenetek végeredménye 0101 lesz. Így már nem is olyan bonyolult Na, ha már ilyen sokáig eljutottunk, akkor felhasználhatjuk ezt a kódot arra, hogy az alapjait figyelembe véve elkészítsük a decode változatot. Mivel 4 értékkel térünk vissza, ezért paraméterként kell majd 4 változó, melyeknek módosíthatjuk az értékét, illetve 1 paraméter pedig a dekódolandó szám. A függvényünk \"címsora\" tehát így néz ki: decode_tires(tires, &tire1, &tire2, &tire3, &tire4) Hogy példánk ne csak elmélet legyen, a tires változó legyen az elõbbi 0101 értékkel egyenlõ. Na akkor menjünk tovább. Bemeneti értékünk ugye a \"tires\" változóban lesz eltárolva, így ezzel fogjuk a mûveleteket végezni. Kezdjünk el gondolkozni. Hogyan is tudnánk ellenõrizni egy adatot. Legegyszerûbb ha a bitenkénti ÉS mûveletet alkalmazzuk. Miért is? Nos, a bitenkénti ÉS mûvelet ugye akkor ad eredményül 1-et, ha mind a 2 érték egy. Tehát ha min egy számot összehasonlítunk a 1-el(binárisan ugye 00000000000000000000000000000001, akkor az eredményünk lényegében arról fog tájékoztatni, hogy az elsõ bit 0, vagy 1. Nem is olyan bonyolult ez. Nézzük akkor az elsõ bitet. Arra emlékezhetünk még, hogy az elsõ bit a tire1, vagyis jobb hátsó abroncs adatait tárolja. Tehát: tire1 = tires & 1; Vagy konkrétan a példánk esetében: tire1 = 0101 & 0001 Mivel a bitenkénti ÉS mûvelet akkor ad 1-et eredményül, ha mind a kettõ bit 1, akkor már láthatjuk is, hogy miért elegendõ az elsõ bitet szemügyre venni. Mivel mind a 2 helyen az elsõ bit 1, ezért a tire1 változónk értéke is 1(0001) lesz. Egyszerû és nagyszerû, máris megkaptuk a jobb hátsó abroncs állapotát.Gondolkozzunk hát tovább. Nekünk most arra van szükségünk, hogy a 2. bit értékét megkapjuk. Hogyan kaphatjuk ezt meg? Legegyszerûbb megoldás, ha a tires változót jobbra shifteljük 1-el, majd ismételten bitenkénti ÉS mûveletet végzünk. Nézzük tehát, hogyan is nézne ez ki: tire2= tires >> 1 & 1; Vagy konkrétan a mi példánkban: tire2 = 0101 >> 1 & 0001vtire2 = 0010 & 0001 Ismételten bitenkénti ÉS mûvelet, és az eredményünk 0, mivel csak az egyik bit 1, a másik 0, így a bitenkénti ÉS mûvelet eredménye is 0. Ezen az elven megcsináljuk a maradék 2 abronccsal is: tire3= tires >> 2 & 1;tire4= tires >> 3 & 1; Vagy a mi esetünkben konkrét példánkkal: tire3 = 0101 >> 2 & 0001 // azaz: --> 0001 & 0001 = 1tire4 = 0101 >> 3 & 0001 // azaz: --> 0000 & 0001 = 0 A teljes kódunk tehát: decode_tires(tires, &tire1, &tire2, &tire3, &tire4){ tire1 = tires & 1; tire2 = tires >> 1 & 1; tire3 = tires >> 2 & 1; tire4 = tires >> 3 & 1;} A konkrét példánkban a 4 változónk értéke a függvény lefutása után: tire1: 1tire2: 0tire3: 1tire4: 0 Ha összehasonlítjuk, ezek pont azok az értékek, mint amiket az encode függvényben megadtunk, tehát úgy tûnik jól dolgoztunk 2.3 encode_lightsEz a rész lesz a legrövidebb. Az egész lámpás rész pontosan ugyanarra a sémára épül mint az abroncsok. Az adott bitek ugyanazon a \"helyen\" lévõ lámpa adatait szolgáltatják, ugyanúgy 0 az ép, és 1 a sérült lámpák állapot azonosítója, és ugyanúgy 1-1 bit tárolja az értékeket.Maga a kód: encode_lights(light1, light2, light3, light4){ return light1 | light2 << 1 | light3 << 2 | light4 << 3;} Láthatjuk hogy halál pontosan ugyanaz mint az encode_tires, csak más változónevekkel. Így hát a decode függvény is ugyan az lesz, csak a változóneveket kell módosítanunk: decode_lights(lights, &light1, &light2, &light3, &light4){ light1 = lights & 1; light2 = lights >> 1 & 1; light3 = lights >> 2 & 1; light4 = lights >> 3 & 1;} Ha megnézzük látjuk, hogy teljesen ugyan az a séma, ugyan az a felépítés, minden megegyezik, ezért nem is írtam le újból ugyanazt, amit egyszer már kifejtettem.2.4 encode_panelsHáttér információ:A SA:MP 7 panelrõl tárol adatokat. Egy panel adatait 4 bit írja le. Nos, ennél több infóra nekünk most nincs szükségünk, az elõzõ kód alapján ezen kód is megérthetõ, így az encode verzióhoz nem is nagyon szeretnék magyarázatot fûzni. A különbség az encode_tires-hez képest, hogy itt 4-essével shiftelünk, mivel 1 panel adatait 4 bit tárolja.Maga a kód: encode_panels(flp, frp, rlp, rrp, windshield, front_bumper, rear_bumper){ return flp | frp << 4 | rlp << 8 | rrp << 12 | windshield << 16 | front_bumper << 20 | rear_bumper << 24;} Nézzünk egy decode verziót, ehhez a kódhoz. Alapjaiban ugyanazon a vonalon kell elindulnunk, mint a decode_tires esetében. Itt most 7 visszatérési értékünk lesz, és 1 bemeneti érték. Tehát így néz ki a függvény header: decode_panels(panels, &flp, &frp, &rlp, &rrp, &windshield, &front_bumper, &rear_bumper) A folytatásban megint csak az elõbbi verziót kell alapul vennünk, azonban itt lesz egy kis különbség. Egyértelmû, hogy mivel 1 panel adatait 4 bit tárolja, így a 4 bitet nekünk egyben kell lekérni, és egyben kell vele visszatérnünk. Megint csak a bitenkénti ÉS mûveletet fogjuk alkalmazni, azonban mivel 4 bitrõl van szó, így egy olyan számmal kell összehasonlítanunk, mely bináris alakban 1111. Ez a szám nem más, mint a 15. Hogy miért ez kell?Ha egy bitsorozat tartalmát ki szeretnénk másolni egy másik változóba, akkor a bitenkénti ÉS mûveletre vna szükségünk. A bitenkénti ÉS mûvelet akkor ad 1-et eredményül, ha mindkét bit 1, ha valamelyik 0, akkor a visszatérés már 0 lesz. Ergó tehát, ha egy csupa 1-esekbõl álló bitsorozattal vetjük össze az eredeti bitsorozatunkat, akkor lényegében lemásoljuk a sorozatot, egy másik változóba, hisz eredményül mindig az eredeti sozatot fogjuk visszakapni. Íme néhány példa: 0101 & 1111 = 01011111 & 1111 = 11110000 & 1111 = 00001011 & 1111 = 1011 Láthatjuk tehát, hogy ha csupa 1-est tartalmazó bitsorozattal hasonlítjuk össze az eredeti számunkat, akkor lényegében egy másolatot készítünk. Mivel nekünk erre a 4 bitre van szükségünk a 32-bõl, így pontosan ezt kell tennünk, ezen 4 bitet lemásolni egy másik változóba, hogy késõbb ezt majd egy másik függvénnyel még jobban darabokra szedhessük.Íme tehát akkor a kódunk elsõ sora: flp = panels & 15; Mivel 4-esével vizsgáljuk a biteket, így lényegében annyit kell még változtatnunk az elõzõ kódon(decode_tires), hogy az 1-esével növekvõ shiftelések helyett 4-esével shifteljünk. Azaz a 2. panel adatainak lekérdezése: frp = panels >> 4 & 15; Innen pedig már csak mindig további 4 shiftelést el kell végeznünk, és szépen végig haladhatunk a panels változó bitjein, és megkaphatjuk minden panel adatait külön-külön. rlp = panels >> 8 & 15;rrp = panels >> 12 & 15;windshield = panels >> 16 & 15;front_bumper= panels >> 20 & 15;rear_bumper = panels >> 24 & 15; Ezennel minden panel adatait megkaptuk. Ezután már akár a decode_tires függvényt felhasználva szét lehet szedni az adatokat darabokra, de akár egy saját névvel, és más változó nevekkel ellátott másik függvényt is létre lehet hozni a decode_tires mintájára.Tehát az egész kód egyben: decode_panels(panels, &flp, &frp, &rlp, &rrp, &windshield, &front_bumper, &rear_bumper){ flp = panels & 15; frp = panels >> 4 & 15; rlp = panels >> 8 & 15; rrp = panels >> 12 & 15; windshield = panels >> 16 & 15; front_bumper= panels >> 20 & 15; rear_bumper = panels >> 24 & 15;} Igazából a függvény ugyanarra a sémára épül mint a decode_tires, csak annyi a különbség, hogy 4 bitenként kapjuk az adatokat, nem pedig egyesével, mivel itt minden panelnek 4 adatát tudjuk majd még lekérdezni. Nem tudom lehet-e még ehhez a részhez hozzáfûzni valamit, szóval szerintem ugorjunk. 2.5 encode_doorsHáttér információ:A SA:MP képes minden jármû 4 \"ajtajának\" állapotát tárolni. Ezek az \"ajtók\"(Doors-t hogy fordíthatnám jobban?) konkrétan a motorháztetõ, csomagtartó, vezetõoldali ajtó, és anyósülés oldali ajtó. A 2 hátsó ajtó állapotát jelenleg még nincs lehetõségünk lekérdezni.Amikor lekérdezzük egy jármû \"ajtajának\" adatait, azt a SA:MP 4 bájtként(32 bit) adja vissza. Ebbõl a 4 bájtból 1 bájt(8 bit) jut minden egyes objektumra. Vagyis minden egyes objektum adatait 8 bit tárolja. Vagyis tárolhatná...A SA:MP csak az elsõ 3 bitet használja, a további bitek üresek lesznek, nincs funkciójuk, talán majd késõbb lesz. Az elsõ bit azt tárolja, hogy az objektum zárt[0], vagy nyitott[1](nem az jelenti, hogy bezárt, hanem becsukott/kinyitott) állapotban van-e. A második bit azt adja meg, hogy az objektum ép[0] vagy sérült[1]. A 3. bit arról tájékoztat, hogy az adott objektum rajta van-e még a jármûvön[0] vagy már leesett[1]. A többi bit mint mondtam üres. Ez majd a decode függvénynél lesz fontos.Nézzük tehát a konkrét kódot: encode_doors(bonnet, boot, driver_door, passenger_door, behind_driver_door, behind_passenger_door){ #pragma unused behind_driver_door #pragma unused behind_passenger_door return bonnet | boot << 8 | driver_door << 16 | passenger_door << 24;} A 2 hátsó ajtóval nem foglalkozunk, mivel azok jelenleg még nem támogatottak ugyebár, de a kód készítõje gondolt a jövõre is, vagy fogalmam sincs miért adta meg ezt a két paramétert is, hisz ígyis-úgyis módosítani kell a kódon... Ha behoznak még 2 ajtót, akkor az 1 bájtos tárolással már 6 bájt lenne, de 1 változó maximum 4 bájt lehet, tehát a kódot úgyis módosítani kell, ha meg arra gondoltak, hogy legalább a paramétereket nem kell majd szegény scriptereknek minden sorban átírni, az megint hülyeség, hisz ki adna értéket olyan paraméternek, amely nincs használva?...Nos mindegy, nem a kritizálásért vagyunk most itt. Ha megnézzük, a kód 8-as shiftelést használ, mivel egy objektum adatait 8 bit tárolja. A mûködési elv ugyanaz, mint az elõzõ encode függvényeknél, így erre nem kívánok megint kitérni.Nézzük azonban hogyan írnánk ehhez egy decode függvényt. Mikor megkérdeztem errõl ismerõsömet, õ azt mondta, ugyanúgy mint az elõzõ, decode_panels függvénynél, csak 8-as shiftelést használunk, és 15 helyett 255-el végezzük a bitenkénti ÉS mûveletet. 255-el? minek?No erre is ki fogok térni késõbb, de haladjunk a szokásos módon. 1 bemeneti paraméterünk van, és 4 kimeneti, vagyis a header rész a következõ: decode_doors(doors, &bonnet, &boot, &driver_door, &passenger_door) Ezzel meg is vagyunk, itt nem látok különösebb magyarázni valót. Én most nem fogok törõdni a 2 nem létezõ ajtóval, ha egy jövõbeni verzióban netán azokat is behoznák mint lekérdezhetõ objektum, akkor ígyis-úgyis módosítani kell majd a kódot, addig meg úgysincs rá szükségünk.Nos, mint az eddigi decode függvényeknél, elsõ feladatunk egy shiftelés mentes bitenkénti ÉS mûvelet elvégzése. Az egyik megoldás a következõ: bonnet = doors & 255; Lehet így is. Azonban véleményem szerint felesleges itt egy ekkora számmal \"szenvednünk\". Miért használjunk ekkora számot, és ezáltal bonyolítsuk feleslegesen a kódunkat, ha nekünk a 8 bitbõl csak az elsõ 3 lesz ami adatot tárol? Bõven elegendõ egy olyan szám, amely bináris alakja 111. Ez nem más, mint a 7. Tehát a következõ megoldás ugyanúgy helyes: bonnet = doors & 7; A két kód pontosan ugyanazt az eredményt fogja visszaadni, semmi különbség a kettõ között. Csupán elsõ esetében nagy számmal dolgozunk, még második verzió esetén csak akkorával, amekkora szükséges.A kód többi részében csak 8-as shifteléssel végigcammogunk a doors változó többi bitjén is: boot = doors >> 8 & 7;driver_door = doors >> 16 & 7;passenger_door = doors >> 24 & 7; A teljes kód egyben: decode_doors(doors, &bonnet, &boot, &driver_door, &passenger_door){ bonnet = doors & 7; boot = doors >> 8 & 7; driver_door = doors >> 16 & 7; passenger_door = doors >> 24 & 7;} A kód pont ugyanazon az elven épül fel, mint az összes többi decode függvényünk, csak épp a benne használatos értékek az \"ajtó\" objectumok adatstruktúrájához vannak igazítva.2.6 Egyéb ötletek gyakorlati alkalmazásra, hasznosításraJoggal felmerülhet a kérdés, hogy ennyi? Emiatt rágtuk végig magunkat ezen néhány tízezer karakteren? Nem. Nem ennyi. A SA:MP-ban rengeteg felhasználási módja lehet még a bitmûveleteknek. Mutatok is néhány dolgot, mire jók még.2.6.1 RGBA->HEX; HEX->RGBA konvertálásokNos, bármily \"hihetetlen\", erre is használható. Miért is? Nos, 16-os számrendszerben 0-9, valamint A-F \"szánjegyek\" léteznek. A hexadecimális számjegy decimális értékben 0-15 lehet. Ha megnézzük bináris értékeket, 0-15 az 4 bittel leírható, hiszen a 15 az bináris alakban 1111. De ha józan paraszti ésszel belegondolunk is. 16 az 24. Így tehát 1 hexadecimális érték, az 4 bináris értékkel írható le. Ebbõl már sejthetõ, hogy itt is shiftelésre lesz szükségünk. Naná Mivel 4 bit ír le egy hexadecimális számjegyet, így 4-esével kell majd shiftelnünk.Mi is az az RGBA?RGBA: A színek olyan meghatározási módja, melyben a 3 alapszín([r]ed, [g]reen, lue) mellett egy 4. érték, az úgynevezett [a]lpha is szerepel, amely az áttetszõséget adja meg. A legtöbb program(pl. paint, PS), és pl. a css(külön függvénnyel!) is elfogadja ezen értékeket decimális számokként(0-255) is, a SA:MP-ban azonban hexadecimális alakban kell megadnunk. Bármily furcsa a következõ 4 sor pont ugyan azt jelenti:\"Normál\" RGB alak(pl. paint, photoshop): Piros: 0, Zöld: 255, Kék: 255CSS alak(függvénnyel): rgba(0, 255, 255, 1);Pawn alak(1): 0x00FFFFFFPawn alak(2): 16777215 Ó igen, a Pawn-ban decimális számokként is megadhatjuk a színeket, hisz a program mindent bináris számrendszerbe fog átszámolni. De akár bináris alakban is megadhattuk volna:Pawn alak(3): 0b111111111111111111111111 Az, hogy mi a Pawn-nak hexadecimális alakban szoktuk megadni, annak az egyetlen oka, hogy így a leginkább \"átlátható\", és ez egy amúgy is elfogadott módja (mármint a hexadecimális számmal való megadás) a színek leírásának a számítástechnikában.Nos, mivel 1 szín 0 és 255 közötti értéket tartalmaz a piros, zöld és kék színekbõl, ez hexadecimálisan 2 számjeggyel leírható: 00-FF. Az FF értéke 255, aki nem hiszi, számoljon utána (162-1)2 hexadecimális számjegy az pedig 8 bináris számjeggyel leírható(00000000-11111111). Így már láthatjuk, hogy 8-as shiftelésre lesz szükségünk. Másra nincs is szükségünk, az RGBA->HEX konverter elkészítéséhez.stock RGBAToHex( r, g, b, a ){return a | b << 8 | g << 16 | r << 24;} Az, hogy miért fordított sorrendben kellett megadnunk az értékeket, az megint csak azzal magyarázható, hogy a biteket hétköznapi értelemben véve visszafelé számozzuk.Egy ellentét, azaz HexToRGBA függvény megírása sem túl bonyolult, ugyan ezt kell csinálnunk, csak visszafelé.stock HexToRGBA( colour, &r, &g, &b, &a ){a = colour & 0xFF;b = (colour >> 8 ) & 0xFF;g = (colour >> 16 ) & 0xFF;r = (colour >> 24 ) & 0xFF;} A 0xFF-et természetesen decimális értékkel is megadhatjuk(255).No akkor lehet ugye RGBA-t ARGB-be is konvertálni, az így néz ki:RGBAToARGBHEX(r,g,b,a){return (r & 0xff) << 24 | (g & 0xff) << 16 | (b & 0xff) << 8 | a & 0xff;} Ez máris egy újabb felhasználási módja a bitmûveleteknek. De, mutatok még 2.6.2 Boolen változók helyett IntegerAz al-al-alcímet elolvasva joggal felmerülhet a kérdés, hogy ez meg hogy jön ide. Nos ez úgy jön ide, hogy most egy kis memóriatakarékosságról fogok itt regélni. Adhattam volna azt is címnek, hogy miért ne használjunk boolean változókat, ha legalább 5 igaz/hamis értéket el akarunk tárolni.Nos akkor egy kis memórialecke. Azt már tisztáztuk, hogy az integer változók 32 bitesek, vagyis 4 bájtot foglalnak le a memóriában maguknak. A boolean típusú változókról azonban még nem volt szó. Mivel 1 értéket tárolnak csak, az is csak igaz vagy hamis lehet, így joggal gondolhatnánk, hogy 1 bit a mérete. Azonban nem...1 boolean változó 8 bitet, azaz 1 bájtot foglal le magának a memóriában. Hogy ez miért így van, az a számítógépek memóriahasználatával magyarázható. Manapság nincs mód arra, hogy a memóriában egyetlen bitet módosítsunk, csak 8-as \"csomagokban\", vagyis bájtonként tudjuk ezt megtenni. Hogy ez miért van így, azt most nem fogom itt fejtegetni Nos, ha megnézzük tehát, a boolean változó egy nagyon-nagyon pazarló megoldás. Hiszen 1 darab boolean változó 1 bájtot foglal, 4 darab boolean változó esetén ez már 4 bájt, ami megegyezik 1 integer változó méretével. Azonban, még a 4 boolean változóban 4 igaz/hamis érték kerülhet tárolásra, addig egy integerben 32 igaz/hamis értéket is eltárolhatunk.Bizony, 32 igaz/hamis érték. Hiszen 1 integer változó 32 bitbõl áll, ha minden bitet hasznosítunk, akkor a bitmûveletek felhasználásával 32 igaz/hamis értéket is eltárolhatunk. Összehasonlítva:32 darab boolean változó mérete 32 bájt.1 integer változó mérete: 4 bájt. Vagyis 32 boolean változó esetén nyolcad annyi memóriánkba kerül, ha egy darab integerben tároljuk el az értékeket. Persze 32 booleanra ritkán van szükség, de egy RP modban simán elõfordulhat. Gondoljunk csak bele... Van 32 boolean-tömbös változónk, ez a maximum slotszámot(1000) figyelembe véve: 32*1000 byte azaz 32000 byte ami ~31 kilobyte. Ha ezt egyetlen integer-tömbbel oldanánk meg: 4*1000 byte, azaz 4000 byte, ami ~3,9 kilobyte. Azért így nézve már nem annyira \"elhanyagolandó\" az a különbség szerintem =)Akár írhatnánk erre külön egy függvényt, mely az adott bit értékével tér vissza. Nem is bonyolult. Így már azok számára is használhatóak a bitmûveletek, akik nem értenek hozzá, és rengeteg memóriát takaríthatnak meg.Íme egy függvény az adatok kiolvasására:stock GetData(integer, bit){ return integer >> bit & 1; //vagy az 1 helyett akár 0x01 alakban is megadhatjuk, nincs jelentõsége} És a párja, adat írásra:stock SetData(&integer, bit, value){ integer |= value << bit; return 1;} Persze ezt még lehetne csinosítani, hogy pl. ne 0-tól hanem 1-tõl számoljuk a biteket, vagy hogy 31(vagy 32 ha 1-tõl számolunk) felett 0 legyen a return értéke, esetleg a value-re is rakhatnánk ellenõrzést, hogy 0-1-e az értéke, vagy más, de ilyenekkel most nem törõdtem.2.6.3 Több érték, egy integerbeNa ez az a rész, ami véleményem szerint már csak a nagyon megrögzött memóriafelszabadítók szerint nem lesz felesleges Amennyiben nincs szükségünk túl nagy számokra(pl. csak 0-15, vagy akár csak 0-7, de akár még a 0-255 is megfelel), akkor akár egy integerbe is tehetjük õket. Hogyan? Egyszerû. Csoportosítjuk a biteket. Tegyük fel, hogy maradván egy RP szervernél, el akarjuk tárolni ûbergigantikusan memóriakímélõ megoldással a játékos házának számát(ez legyen mondjuk 0-100 közötti szám), a játékos frakcióját(ez legyen 0-15 között), a játékos rangját(0-7 között), hogy a telefonja be van-e kapcsolva(0-1), a játékos szolgálati jármûvének id-jét(legyen 0-255 között), és az alapmunkáját(0-15 között). Ez is megoldható, egyetlen integerben, és ismételten 1000 slottal számolva máris megspóroltunk 4*4*1000+1*1000 byte-t(5 integer, és 1 boolean helyett csupán 1 integert használunk), ami ~16,6 kilobyte. Azért nem mindegy az, hogy 20,5 kb-ot vagy 4 kb-ot használunk szerintem... Ezt a következõ módszerrel valósíthatnánk meg:#define DATA_HOUSE 0#define DATA_FACTION 1#define DATA_RANK 2#define DATA_PHONESTATE 3#define DATA_DUTYCAR 4#define DATA_JOB 5stock GetData(playerinfo, data){ switch(data) { case DATA_HOUSE: return playerinfo & 127;//7 bit case DATA_FACTION: return playerinfo >> 7 & 15;//4 bit case DATA_RANK: return playerinfo >> 11 & 7;//3 bit case DATA_PHONESTATE: return playerinfo >> 14 & 1;//1 bit case DATA_DUTYCAR: return playerinfo >> 15 & 255;//8 bit case DATA_JOB: return playerinfo >> 23 & 15;//4 bit}return -1;}stock SetData(&playerinfo, data, value){ switch(data) { case DATA_HOUSE: { playerinfo = playerinfo & ~127 | value; }//7 bit (0-127 közötti maximálisan) case DATA_FACTION: { playerinfo = playerinfo & ~(15 << 7) | value << 7; }//4 bit (0-15 között) case DATA_RANK: { playerinfo = playerinfo & ~(7 << 11) | value << 11; }//3 bit (0-7 között) case DATA_PHONESTATE: { playerinfo = playerinfo & ~(1 << 14) | value << 14; }//1 bit (0-1) case DATA_DUTYCAR: { playerinfo = playerinfo & ~(255 << 15) | value << 15; }//8 bit (0-255 között) case DATA_JOB: { playerinfo = playerinfo & ~(15 << 23) | value << 23; }//4 bit (0-15 között) } return -1;}stock SetDatas(&playerinfo, house, faction, rank, phonestate, dutycar, job){playerinfo |= house | (faction << 7) | (rank << 11) | (phonestate << 14) | (dutycar << 15) | (job << 23);} Ez már azért elég morbid, ezt inkább csak érdekességképp mutattam, de éppenséggel ez is használható lenne, csak ehhez már kell egy kis õrültség 2.7 Második fejezet végeNos, láthattuk hogyan is mûködik mindazon sok \"marhaság\", amit az elsõ fejezetben elméletként átvettünk. Láthattuk hogyan mûködnek a jármûvek paneljének adataival babráló encode függvények, valamint megmutattam, hogyan írhatunk hozzájuk decode verziókat. Láthattunk még példát egyéb gyakorlati hasznosításra, kicsit megkoccoltuk a hexadecimális számok világát is, majd szóba került egy kis memóriatakarékosság. Végül mutattam egy összetettebb, elvetemült példát is, a bitmûveletek hasznosítására.3. Összegzés, végszó Úgy gondolom, hogy elég részletesen körbe jártuk a témát. Megnéztük a bitmûveletek elméleti, és gyakorlati oldalát is, láthattunk gyakorlati példákat. Nem kell elsõre megérteni ezt az egészet, sõt... Elegendõ ha egyszer elolvassuk, emésztjük kicsit, majd késõbb megint elõvesszük, akkor netán már kicsit többet is megértünk belõle. Nem egy kihagyhatatlan része a Pawno-nak, de azért van annak haszna, ha az ember jártas ebben a témában is, bár sokak szerint nem éri meg a sok fáradtságot, és idõt, mely a megértéséhez, begyakorlásához, használatának elsajátításához szükséges. Próbáltam mindent részletesen kifejteni. A bonyolultabb dolgokhoz sokat magyarázni, az egyszerûbbek esetén csak visszautalni az alapokhoz. Sok dolgot talán már túl is magyaráztam. Ezt a leírást nem 1 nap alatt készítettem el, kb. 1 hónapja írtam meg az elméleti oldalát, most az elmúlt pár napban pedig a gyakorlatot. Esténként 1-2 órát szántam rá csupán, és mivel ilyenkor már nem vagyok épp a toppon, elképzelhetõ, hogy néhány helyen helyesírási, vagy elvi hibát vétettem. Amennyiben valaki ilyet találna, kérem szóljon, és korrigálni fogom. Nos, azt hiszem nincs is más hátra, mint hogy sok szerencsét kívánjak a bitmûveletek elsajátításához, és megosszam a forrásokat. Remélem néhányan hasznosnak találják, eme igen hosszúra sikerült leírást.4. Források: Internet:WikipédiaSA:MP WikiSA:MP hivatalos fórumKönyv, e-book:Pohl László - A programozás alapjai(BME - Elektronikus Eszközök Tanszéke, Budapest, 2010)Siroki László - C programozás - Bitmûveletek jegyzeteˆ Anthony, 2012 - Minden jog fenntartva.A leírás egészben, vagy részleteiben szabadon terjeszthetõ, ameddig egyértelmûen fel van tüntetve az eredeti készítõ, valamint hogy honnan is származik. « Utoljára szerkesztve: 2012. augusztus 01. - 10:53:54 írta Anthony » Naplózva
~0000000000000000000000000000011111111111111111111111111111111000[/quote]Eredményül most 11111111111111111111111111111000-t kaptunk, ami a kettes számrendszerben a -8-nak felel meg. A következõ 2 sor tehát ugyanazt jelenti: Bin:~00000000000000000000000000000111=11111111111111111111111111111000Dec:~7=-8[/quote]Megint néhány példa: ~00000000000000000000000000000101 = 11111111111111111111111111111011~00000000000000000000000001110110= 11111111111111111111111110001010[/quote]Ehhez kapcsolódóan lenne itt egy fontos megjegyzendõ dolog. Aki nem érti, hogy miért -8 a megoldás, az nem olvasta el elég rendesen a \"Részletesebben a bitekrõl a Pawn-ban\" részt.1.4.5 Biteltolás (arithmetic shift, shiftelés) operátor(ok)A shift operátorok(<<, >>) a bitek tologatására használhatóak. A << balra tol, a >> jobbra. Bal oldalt található a szám, amit tologatni kívánunk, jobb oldalt pedig az, hogy mennyivel szeretnénk eltolni.1.4.5.1 Jobbra toló operátor(>>):Ez az operátor a szám bitjeit jobbra tolja a megadott mértékkel. Egy bittel való eltolás, 2-vel való osztásnak felel meg. Nézzünk egy példát: 00000000000000000000000000001000//8>>200000000000000000000000000000010//2[/quote]Láthatjuk, hogy semmi más nem történt, mint minden bitet 2-vel hátrébb mozgattunk. Az így \"megüresedett\" bitek helyére pedig 0-k kerültek. Lényegében az történt, hogy az utolsó 2 bit kiesett, az így megmaradt biteket 2-vel hátrébb csúsztattuk, majd az elsõ 2, így \"üres\" bit 0 értéket kapott.Nézzünk egy példát negatív számmal: 11111111111111111111111111110000//-8>>211111111111111111111111111111100//-2[/quote]Láthatjuk, hogy itt fontos szerepe van az MSB-nek. Ettõl függ ugyanis, hogy 0-k vagy 1-esek kerülnek a \"megüresedett\" bitek helyére. Fontos tehát megjegyezni, hogy a jobbra shiftelés megtartja a számok elõjelét, nem változtat azon!1.4.5.2 Balra toló operátor(<<):Ez az operátor a szám bitjeit balra tolja a megadott mértékkel. Egy bittel való eltolás, 2-vel való szorzásnak felel meg. Nézzünk egy példát: 00000000000000000000000000001000//8<<200000000000000000000000000100000//32[/quote]Itt megint könnyedén észrevehetõ mi is történt. Minden bitet fentebb \"toltunk\" 2-vel. Azok a bitek, amelyek ezáltal a 32. bit elé kerültek, kiesnek. Az így megüresedett bitek 0-kkal egészültek ki. Eddig nem túl bonyolult. Azonban figyeljünk oda, ha túl sokáig shiftelünk egy pozitív számot, akkor az utolsó érték, melyet fel tud venni a változónk (mielõtt még túlcsordulás miatt elérné a 0-t), az egy negatív szám!Nézzük mi történik negatív számok esetén: 11111111111111111111111111111000//-8<<211111111111111111111111111100000//-32[/quote]Láthatjuk hogy a balra shifteléskor a szám mindig 0-kkal egészítõdik ki, legyen az akár negatív, akár pozitív. Nem veszi figyelembe az MSB értékét, így ha túl sokat shiftelünk, akkor elõfordulhat, hogy a szám elveszti negatív értékét, és eléri a 0-t.1.4.6 Logikai biteltolás (logical shift, logikai shiftelés) operátorElõször is le kell tisztázni, hogy logikai shiftelésbõl csak egy létezik, jobbra logikai shiftelés(>>>). Sokban hasonlít a jobbra shiftelõ operátorra (>>), azonban van egy fontos különbség.Nézzük az elsõ példát: 00000000000000000000000000001000//8>>>200000000000000000000000000000010//2[/quote]Eddig nem igazán tapasztalhattunk semmi változást, minden pont ugyanúgy zajlott, mint a jobbra shiftelésnél(>>).Nézzük mi történik negatív szám esetén: 11111111111111111111111111111000//-8>>>200111111111111111111111111111110 //1073741822[/quote]Itt már láthatjuk a különbséget. Logikai shiftelés esetén az üres bitek helyére bekerülõ érték nem függ az MSB-tõl, hanem mindig 0. Emiatt soha nem kaphatunk eredményül negatív számot, logikai shiftelés esetén.1.5 Elsõ fejezet végeNos, ezennel átvettünk mindent, amit érdemes tudni a bitmûveletekrõl. Megmutattam az alapok, hogy hogyan épülnek fel a változók, valamint átvettük a fõ operátorokat. Eddig csak színtiszta elmélet volt. A következõ részben megmutatom, hogyan hasznosíthatjuk mindezt a gyakorlatban, vagyis mire is jó az a sok marhaság, amit eddig leírtam.2. Gyakorlati példák, érdekességek 2.1 Áttekintés, mikrõl is lesz még szóAz alábbiakban még pár dolgot szeretnék mutatni. Elõször is néhány \"függvénnyel\" fogok foglalkozni, majd mutatok még egy-két érdekességet. Konkrétan megmutatom, hogyan tudjuk egy jármû minden \"paneljének\" adatát kiíratni, részletesen(erre alapból nincs lehetõség, és a függvények is mind \"encode\" függvények, én viszont mutatok egy-egy decode \"verziót\" is.)Az alábbi 4 függvénnyel fogok foglalkozni: encode_tiresencode_panelsencode_doorsencode_lightsDe elõbb egy kis háttérinfó:A Pawnban a forrásban megadhatunk bináris számokat ugyanúgy, ahogyan Hex számokat is megadhatunk. Ehhez a \"0b\" prefixet kell használnunk. Tehát ha bináris számunk az 1011 és ezt akarjuk adni értékül egy változónak, azt így tehetjük meg: new var = 0b1011; 2.2 encode_tiresHáttér információ:A SA:MP-ban egy jármû abroncsainak adataid 4 bit tárolja. Az elsõ bit a jobb hátsó, a második bit a jobb elsõ, a harmadik bit a bal hátsó, a negyedik bit pedig a bal elsõ abroncsról tárol információt. Mivel 4 bitben eltárolhatóak az információk, így decimális formában 0-15 értékekkel is be lehet állítani a jármûvek abroncsait. 16-tól kezdve az értékek már ismétlõdni kezdenek, szóval a 16-nál az eredmény ugyanaz mint 0-nál, 17-nél ugyanaz mind 1-nél, és így tovább. Ez érthetõ, ha tudjuk hogyan épül fel a kettes számrendszer.(Ne felejtsük el, a biteket hátulról számozzuk!)Nos nézzük hogyan néz ki a kód: encode_tires(tire1, tire2, tire3, tire4){ return tire1 | tire2 << 1 | tire3 << 2 | tire4 << 3;} Láthatjuk, hogy bekérjük a 4 abroncs adatát(vagyis hogy ép[0], vagy kidurrant[1]), majd egy sima bitenkénti VAGY mûvelet és biteltolás kombinálásával visszatérünk az eredménnyel. A kódban a tire1 felel meg a jobb hátsó abroncsnak, a tire2 a jobb elsõ, a tire3 a bal hátsó, és a tire4 a bal elsõnek. Nézzünk egy konkrét példát. Tegyük fel, hogy én a hátsó 2 abroncsot ki akarom durrantani, még az elsõ kettõt épre akarom beállítani. Ez esetben így néz ki a dolog: encode_tire(1,0,1,0); Ha kiíratnánk konzolba a visszatérési értéket (%d-vel), 5-öt kapnánk. Miért is? Az 5 bináris alakja 0101. Hoppá, ha megnézzük ez pont fordítottja annak, amit mi megadtunk számjegyekként, csak már konkrét bináris számként. Nézzük hogyan is történhetett mindez, mit is csináltunk. A függvény a következõ mûveletet végzi: tire1 | tire2 << 1 | tire3 << 2 | tire4 << 3 Konyhanyelven úgy magyarázhatnánk el a dolgot, hogy ez a kód egymás mellé pakolgatja a megadott számokat, fordított sorrendben. Na de mi most nem a konyhanyelv miatt vagyunk ebben a leírásban, szóval nézzük a kódunkat. Lényegében ez futott le, a konkrét értékeinkkel nézve: 1 | 0 << 1 | 1 << 2 | 0 << 3 Bármily furcsa is, ez egy mûvelet, melyen szépen végig halad sorban a program. Elõször is írjuk át az egészet bináris alakba(az egyszerûség kedvéért most csak 4 bitet fogok kiírni): 0001 | 0000 << 1 | 0001 << 2 | 0000 << 3 Na erre se mondtuk volna rá a tutorial elsõ részének elolvasása elõtt, hogy ez egy mûveletsor. A rendszer elsõnek az eltolásokat végzi el (olyan ez mint a 2*3+2*4+2*5 mûveletben elõször a szorzásokat végezzük el, és csak utána adunk össze, ha nincs zárójel), és csak utána a bitenkénti VAGY mûveleteket, ezért mi is így fogunk tenni. Emlékezzünk, eltoláskor az \"üres\" bitek 0 értéket fognak felvenni. Végezzük el külön csak az eltolásokat: 0000 << 1 = 00000001 << 2 = 01000000 << 3 0000 Nos, ha ezeket elvégeztük, akkor nézzük, hogy is áll most a kódunk: 0001 | 0000 | 0100 | 0000 Négy azonos mûvelet van hátra, így hát a program innen más szépen sorban fog haladni. Tegyünk mi is így. Elsõ tehát a 0001 | 0000 mûvelet elvégzése. Emlékeztetõül, a bitenkénti VAGY mûvelet akkor ad 0-t eredményül, ha mind a kettõ érték 0, minden más esetben 1-et. Végezzük el külön az elsõ bitenkénti VAGY mûveletet: 0001 | 0000 = 0001 Nem volt bonyolult, nézzük tehát hogy áll akkor most a teljes mûveletünk: 0001 | 0100 | 0000 Haladunk szépen tovább, elvégezzük a 0001 | 0100 mûveletet. 0001 | 0100 = 0101 Nincs mit magyarázni rajta, egyszerû bitenkénti VAGY mûvelet volt ez is. Már csak ez van hátra: 0101 | 0000 Nos, ez sem lesz egy bonyolult mûvelet: 0101 | 0000 = 0101 Ezzel meg is kaptuk a végeredményt, ami nem más, mint a 0101 (tízes számrendszerben 5). Mivel azonban eléggé kusza lett így a számításunk, nézzük egyben hogyan is néz ki az egész, ha nem bontom ennyire részekre: 0100 | 0000 << 1 | 0001 << 2 | 0000 << 3v0001 | 0000 | 0100 | 0000v0001 | 0100 | 0000v0101 | 0000v0101 Nos, ezennel láthattuk a gyakorlatban is, hogyan mûködik a függvény. Kijelenthetjük tehát, hogy a függvény a számokat a sorrendjüknek megfelelõ számú bitbe helyezi. Mivel azonban a biteket hátulról számozzuk, így az 1,0,1,0 bemenetek végeredménye 0101 lesz. Így már nem is olyan bonyolult Na, ha már ilyen sokáig eljutottunk, akkor felhasználhatjuk ezt a kódot arra, hogy az alapjait figyelembe véve elkészítsük a decode változatot. Mivel 4 értékkel térünk vissza, ezért paraméterként kell majd 4 változó, melyeknek módosíthatjuk az értékét, illetve 1 paraméter pedig a dekódolandó szám. A függvényünk \"címsora\" tehát így néz ki: decode_tires(tires, &tire1, &tire2, &tire3, &tire4) Hogy példánk ne csak elmélet legyen, a tires változó legyen az elõbbi 0101 értékkel egyenlõ. Na akkor menjünk tovább. Bemeneti értékünk ugye a \"tires\" változóban lesz eltárolva, így ezzel fogjuk a mûveleteket végezni. Kezdjünk el gondolkozni. Hogyan is tudnánk ellenõrizni egy adatot. Legegyszerûbb ha a bitenkénti ÉS mûveletet alkalmazzuk. Miért is? Nos, a bitenkénti ÉS mûvelet ugye akkor ad eredményül 1-et, ha mind a 2 érték egy. Tehát ha min egy számot összehasonlítunk a 1-el(binárisan ugye 00000000000000000000000000000001, akkor az eredményünk lényegében arról fog tájékoztatni, hogy az elsõ bit 0, vagy 1. Nem is olyan bonyolult ez. Nézzük akkor az elsõ bitet. Arra emlékezhetünk még, hogy az elsõ bit a tire1, vagyis jobb hátsó abroncs adatait tárolja. Tehát: tire1 = tires & 1; Vagy konkrétan a példánk esetében: tire1 = 0101 & 0001 Mivel a bitenkénti ÉS mûvelet akkor ad 1-et eredményül, ha mind a kettõ bit 1, akkor már láthatjuk is, hogy miért elegendõ az elsõ bitet szemügyre venni. Mivel mind a 2 helyen az elsõ bit 1, ezért a tire1 változónk értéke is 1(0001) lesz. Egyszerû és nagyszerû, máris megkaptuk a jobb hátsó abroncs állapotát.Gondolkozzunk hát tovább. Nekünk most arra van szükségünk, hogy a 2. bit értékét megkapjuk. Hogyan kaphatjuk ezt meg? Legegyszerûbb megoldás, ha a tires változót jobbra shifteljük 1-el, majd ismételten bitenkénti ÉS mûveletet végzünk. Nézzük tehát, hogyan is nézne ez ki: tire2= tires >> 1 & 1; Vagy konkrétan a mi példánkban: tire2 = 0101 >> 1 & 0001vtire2 = 0010 & 0001 Ismételten bitenkénti ÉS mûvelet, és az eredményünk 0, mivel csak az egyik bit 1, a másik 0, így a bitenkénti ÉS mûvelet eredménye is 0. Ezen az elven megcsináljuk a maradék 2 abronccsal is: tire3= tires >> 2 & 1;tire4= tires >> 3 & 1; Vagy a mi esetünkben konkrét példánkkal: tire3 = 0101 >> 2 & 0001 // azaz: --> 0001 & 0001 = 1tire4 = 0101 >> 3 & 0001 // azaz: --> 0000 & 0001 = 0 A teljes kódunk tehát: decode_tires(tires, &tire1, &tire2, &tire3, &tire4){ tire1 = tires & 1; tire2 = tires >> 1 & 1; tire3 = tires >> 2 & 1; tire4 = tires >> 3 & 1;} A konkrét példánkban a 4 változónk értéke a függvény lefutása után: tire1: 1tire2: 0tire3: 1tire4: 0 Ha összehasonlítjuk, ezek pont azok az értékek, mint amiket az encode függvényben megadtunk, tehát úgy tûnik jól dolgoztunk 2.3 encode_lightsEz a rész lesz a legrövidebb. Az egész lámpás rész pontosan ugyanarra a sémára épül mint az abroncsok. Az adott bitek ugyanazon a \"helyen\" lévõ lámpa adatait szolgáltatják, ugyanúgy 0 az ép, és 1 a sérült lámpák állapot azonosítója, és ugyanúgy 1-1 bit tárolja az értékeket.Maga a kód: encode_lights(light1, light2, light3, light4){ return light1 | light2 << 1 | light3 << 2 | light4 << 3;} Láthatjuk hogy halál pontosan ugyanaz mint az encode_tires, csak más változónevekkel. Így hát a decode függvény is ugyan az lesz, csak a változóneveket kell módosítanunk: decode_lights(lights, &light1, &light2, &light3, &light4){ light1 = lights & 1; light2 = lights >> 1 & 1; light3 = lights >> 2 & 1; light4 = lights >> 3 & 1;} Ha megnézzük látjuk, hogy teljesen ugyan az a séma, ugyan az a felépítés, minden megegyezik, ezért nem is írtam le újból ugyanazt, amit egyszer már kifejtettem.2.4 encode_panelsHáttér információ:A SA:MP 7 panelrõl tárol adatokat. Egy panel adatait 4 bit írja le. Nos, ennél több infóra nekünk most nincs szükségünk, az elõzõ kód alapján ezen kód is megérthetõ, így az encode verzióhoz nem is nagyon szeretnék magyarázatot fûzni. A különbség az encode_tires-hez képest, hogy itt 4-essével shiftelünk, mivel 1 panel adatait 4 bit tárolja.Maga a kód: encode_panels(flp, frp, rlp, rrp, windshield, front_bumper, rear_bumper){ return flp | frp << 4 | rlp << 8 | rrp << 12 | windshield << 16 | front_bumper << 20 | rear_bumper << 24;} Nézzünk egy decode verziót, ehhez a kódhoz. Alapjaiban ugyanazon a vonalon kell elindulnunk, mint a decode_tires esetében. Itt most 7 visszatérési értékünk lesz, és 1 bemeneti érték. Tehát így néz ki a függvény header: decode_panels(panels, &flp, &frp, &rlp, &rrp, &windshield, &front_bumper, &rear_bumper) A folytatásban megint csak az elõbbi verziót kell alapul vennünk, azonban itt lesz egy kis különbség. Egyértelmû, hogy mivel 1 panel adatait 4 bit tárolja, így a 4 bitet nekünk egyben kell lekérni, és egyben kell vele visszatérnünk. Megint csak a bitenkénti ÉS mûveletet fogjuk alkalmazni, azonban mivel 4 bitrõl van szó, így egy olyan számmal kell összehasonlítanunk, mely bináris alakban 1111. Ez a szám nem más, mint a 15. Hogy miért ez kell?Ha egy bitsorozat tartalmát ki szeretnénk másolni egy másik változóba, akkor a bitenkénti ÉS mûveletre vna szükségünk. A bitenkénti ÉS mûvelet akkor ad 1-et eredményül, ha mindkét bit 1, ha valamelyik 0, akkor a visszatérés már 0 lesz. Ergó tehát, ha egy csupa 1-esekbõl álló bitsorozattal vetjük össze az eredeti bitsorozatunkat, akkor lényegében lemásoljuk a sorozatot, egy másik változóba, hisz eredményül mindig az eredeti sozatot fogjuk visszakapni. Íme néhány példa: 0101 & 1111 = 01011111 & 1111 = 11110000 & 1111 = 00001011 & 1111 = 1011 Láthatjuk tehát, hogy ha csupa 1-est tartalmazó bitsorozattal hasonlítjuk össze az eredeti számunkat, akkor lényegében egy másolatot készítünk. Mivel nekünk erre a 4 bitre van szükségünk a 32-bõl, így pontosan ezt kell tennünk, ezen 4 bitet lemásolni egy másik változóba, hogy késõbb ezt majd egy másik függvénnyel még jobban darabokra szedhessük.Íme tehát akkor a kódunk elsõ sora: flp = panels & 15; Mivel 4-esével vizsgáljuk a biteket, így lényegében annyit kell még változtatnunk az elõzõ kódon(decode_tires), hogy az 1-esével növekvõ shiftelések helyett 4-esével shifteljünk. Azaz a 2. panel adatainak lekérdezése: frp = panels >> 4 & 15; Innen pedig már csak mindig további 4 shiftelést el kell végeznünk, és szépen végig haladhatunk a panels változó bitjein, és megkaphatjuk minden panel adatait külön-külön. rlp = panels >> 8 & 15;rrp = panels >> 12 & 15;windshield = panels >> 16 & 15;front_bumper= panels >> 20 & 15;rear_bumper = panels >> 24 & 15; Ezennel minden panel adatait megkaptuk. Ezután már akár a decode_tires függvényt felhasználva szét lehet szedni az adatokat darabokra, de akár egy saját névvel, és más változó nevekkel ellátott másik függvényt is létre lehet hozni a decode_tires mintájára.Tehát az egész kód egyben: decode_panels(panels, &flp, &frp, &rlp, &rrp, &windshield, &front_bumper, &rear_bumper){ flp = panels & 15; frp = panels >> 4 & 15; rlp = panels >> 8 & 15; rrp = panels >> 12 & 15; windshield = panels >> 16 & 15; front_bumper= panels >> 20 & 15; rear_bumper = panels >> 24 & 15;} Igazából a függvény ugyanarra a sémára épül mint a decode_tires, csak annyi a különbség, hogy 4 bitenként kapjuk az adatokat, nem pedig egyesével, mivel itt minden panelnek 4 adatát tudjuk majd még lekérdezni. Nem tudom lehet-e még ehhez a részhez hozzáfûzni valamit, szóval szerintem ugorjunk. 2.5 encode_doorsHáttér információ:A SA:MP képes minden jármû 4 \"ajtajának\" állapotát tárolni. Ezek az \"ajtók\"(Doors-t hogy fordíthatnám jobban?) konkrétan a motorháztetõ, csomagtartó, vezetõoldali ajtó, és anyósülés oldali ajtó. A 2 hátsó ajtó állapotát jelenleg még nincs lehetõségünk lekérdezni.Amikor lekérdezzük egy jármû \"ajtajának\" adatait, azt a SA:MP 4 bájtként(32 bit) adja vissza. Ebbõl a 4 bájtból 1 bájt(8 bit) jut minden egyes objektumra. Vagyis minden egyes objektum adatait 8 bit tárolja. Vagyis tárolhatná...A SA:MP csak az elsõ 3 bitet használja, a további bitek üresek lesznek, nincs funkciójuk, talán majd késõbb lesz. Az elsõ bit azt tárolja, hogy az objektum zárt[0], vagy nyitott[1](nem az jelenti, hogy bezárt, hanem becsukott/kinyitott) állapotban van-e. A második bit azt adja meg, hogy az objektum ép[0] vagy sérült[1]. A 3. bit arról tájékoztat, hogy az adott objektum rajta van-e még a jármûvön[0] vagy már leesett[1]. A többi bit mint mondtam üres. Ez majd a decode függvénynél lesz fontos.Nézzük tehát a konkrét kódot: encode_doors(bonnet, boot, driver_door, passenger_door, behind_driver_door, behind_passenger_door){ #pragma unused behind_driver_door #pragma unused behind_passenger_door return bonnet | boot << 8 | driver_door << 16 | passenger_door << 24;} A 2 hátsó ajtóval nem foglalkozunk, mivel azok jelenleg még nem támogatottak ugyebár, de a kód készítõje gondolt a jövõre is, vagy fogalmam sincs miért adta meg ezt a két paramétert is, hisz ígyis-úgyis módosítani kell a kódon... Ha behoznak még 2 ajtót, akkor az 1 bájtos tárolással már 6 bájt lenne, de 1 változó maximum 4 bájt lehet, tehát a kódot úgyis módosítani kell, ha meg arra gondoltak, hogy legalább a paramétereket nem kell majd szegény scriptereknek minden sorban átírni, az megint hülyeség, hisz ki adna értéket olyan paraméternek, amely nincs használva?...Nos mindegy, nem a kritizálásért vagyunk most itt. Ha megnézzük, a kód 8-as shiftelést használ, mivel egy objektum adatait 8 bit tárolja. A mûködési elv ugyanaz, mint az elõzõ encode függvényeknél, így erre nem kívánok megint kitérni.Nézzük azonban hogyan írnánk ehhez egy decode függvényt. Mikor megkérdeztem errõl ismerõsömet, õ azt mondta, ugyanúgy mint az elõzõ, decode_panels függvénynél, csak 8-as shiftelést használunk, és 15 helyett 255-el végezzük a bitenkénti ÉS mûveletet. 255-el? minek?No erre is ki fogok térni késõbb, de haladjunk a szokásos módon. 1 bemeneti paraméterünk van, és 4 kimeneti, vagyis a header rész a következõ: decode_doors(doors, &bonnet, &boot, &driver_door, &passenger_door) Ezzel meg is vagyunk, itt nem látok különösebb magyarázni valót. Én most nem fogok törõdni a 2 nem létezõ ajtóval, ha egy jövõbeni verzióban netán azokat is behoznák mint lekérdezhetõ objektum, akkor ígyis-úgyis módosítani kell majd a kódot, addig meg úgysincs rá szükségünk.Nos, mint az eddigi decode függvényeknél, elsõ feladatunk egy shiftelés mentes bitenkénti ÉS mûvelet elvégzése. Az egyik megoldás a következõ: bonnet = doors & 255; Lehet így is. Azonban véleményem szerint felesleges itt egy ekkora számmal \"szenvednünk\". Miért használjunk ekkora számot, és ezáltal bonyolítsuk feleslegesen a kódunkat, ha nekünk a 8 bitbõl csak az elsõ 3 lesz ami adatot tárol? Bõven elegendõ egy olyan szám, amely bináris alakja 111. Ez nem más, mint a 7. Tehát a következõ megoldás ugyanúgy helyes: bonnet = doors & 7; A két kód pontosan ugyanazt az eredményt fogja visszaadni, semmi különbség a kettõ között. Csupán elsõ esetében nagy számmal dolgozunk, még második verzió esetén csak akkorával, amekkora szükséges.A kód többi részében csak 8-as shifteléssel végigcammogunk a doors változó többi bitjén is: boot = doors >> 8 & 7;driver_door = doors >> 16 & 7;passenger_door = doors >> 24 & 7; A teljes kód egyben: decode_doors(doors, &bonnet, &boot, &driver_door, &passenger_door){ bonnet = doors & 7; boot = doors >> 8 & 7; driver_door = doors >> 16 & 7; passenger_door = doors >> 24 & 7;} A kód pont ugyanazon az elven épül fel, mint az összes többi decode függvényünk, csak épp a benne használatos értékek az \"ajtó\" objectumok adatstruktúrájához vannak igazítva.2.6 Egyéb ötletek gyakorlati alkalmazásra, hasznosításraJoggal felmerülhet a kérdés, hogy ennyi? Emiatt rágtuk végig magunkat ezen néhány tízezer karakteren? Nem. Nem ennyi. A SA:MP-ban rengeteg felhasználási módja lehet még a bitmûveleteknek. Mutatok is néhány dolgot, mire jók még.2.6.1 RGBA->HEX; HEX->RGBA konvertálásokNos, bármily \"hihetetlen\", erre is használható. Miért is? Nos, 16-os számrendszerben 0-9, valamint A-F \"szánjegyek\" léteznek. A hexadecimális számjegy decimális értékben 0-15 lehet. Ha megnézzük bináris értékeket, 0-15 az 4 bittel leírható, hiszen a 15 az bináris alakban 1111. De ha józan paraszti ésszel belegondolunk is. 16 az 24. Így tehát 1 hexadecimális érték, az 4 bináris értékkel írható le. Ebbõl már sejthetõ, hogy itt is shiftelésre lesz szükségünk. Naná Mivel 4 bit ír le egy hexadecimális számjegyet, így 4-esével kell majd shiftelnünk.Mi is az az RGBA?RGBA: A színek olyan meghatározási módja, melyben a 3 alapszín([r]ed, [g]reen, lue) mellett egy 4. érték, az úgynevezett [a]lpha is szerepel, amely az áttetszõséget adja meg. A legtöbb program(pl. paint, PS), és pl. a css(külön függvénnyel!) is elfogadja ezen értékeket decimális számokként(0-255) is, a SA:MP-ban azonban hexadecimális alakban kell megadnunk. Bármily furcsa a következõ 4 sor pont ugyan azt jelenti:\"Normál\" RGB alak(pl. paint, photoshop): Piros: 0, Zöld: 255, Kék: 255CSS alak(függvénnyel): rgba(0, 255, 255, 1);Pawn alak(1): 0x00FFFFFFPawn alak(2): 16777215 Ó igen, a Pawn-ban decimális számokként is megadhatjuk a színeket, hisz a program mindent bináris számrendszerbe fog átszámolni. De akár bináris alakban is megadhattuk volna:Pawn alak(3): 0b111111111111111111111111 Az, hogy mi a Pawn-nak hexadecimális alakban szoktuk megadni, annak az egyetlen oka, hogy így a leginkább \"átlátható\", és ez egy amúgy is elfogadott módja (mármint a hexadecimális számmal való megadás) a színek leírásának a számítástechnikában.Nos, mivel 1 szín 0 és 255 közötti értéket tartalmaz a piros, zöld és kék színekbõl, ez hexadecimálisan 2 számjeggyel leírható: 00-FF. Az FF értéke 255, aki nem hiszi, számoljon utána (162-1)2 hexadecimális számjegy az pedig 8 bináris számjeggyel leírható(00000000-11111111). Így már láthatjuk, hogy 8-as shiftelésre lesz szükségünk. Másra nincs is szükségünk, az RGBA->HEX konverter elkészítéséhez.stock RGBAToHex( r, g, b, a ){return a | b << 8 | g << 16 | r << 24;} Az, hogy miért fordított sorrendben kellett megadnunk az értékeket, az megint csak azzal magyarázható, hogy a biteket hétköznapi értelemben véve visszafelé számozzuk.Egy ellentét, azaz HexToRGBA függvény megírása sem túl bonyolult, ugyan ezt kell csinálnunk, csak visszafelé.stock HexToRGBA( colour, &r, &g, &b, &a ){a = colour & 0xFF;b = (colour >> 8 ) & 0xFF;g = (colour >> 16 ) & 0xFF;r = (colour >> 24 ) & 0xFF;} A 0xFF-et természetesen decimális értékkel is megadhatjuk(255).No akkor lehet ugye RGBA-t ARGB-be is konvertálni, az így néz ki:RGBAToARGBHEX(r,g,b,a){return (r & 0xff) << 24 | (g & 0xff) << 16 | (b & 0xff) << 8 | a & 0xff;} Ez máris egy újabb felhasználási módja a bitmûveleteknek. De, mutatok még 2.6.2 Boolen változók helyett IntegerAz al-al-alcímet elolvasva joggal felmerülhet a kérdés, hogy ez meg hogy jön ide. Nos ez úgy jön ide, hogy most egy kis memóriatakarékosságról fogok itt regélni. Adhattam volna azt is címnek, hogy miért ne használjunk boolean változókat, ha legalább 5 igaz/hamis értéket el akarunk tárolni.Nos akkor egy kis memórialecke. Azt már tisztáztuk, hogy az integer változók 32 bitesek, vagyis 4 bájtot foglalnak le a memóriában maguknak. A boolean típusú változókról azonban még nem volt szó. Mivel 1 értéket tárolnak csak, az is csak igaz vagy hamis lehet, így joggal gondolhatnánk, hogy 1 bit a mérete. Azonban nem...1 boolean változó 8 bitet, azaz 1 bájtot foglal le magának a memóriában. Hogy ez miért így van, az a számítógépek memóriahasználatával magyarázható. Manapság nincs mód arra, hogy a memóriában egyetlen bitet módosítsunk, csak 8-as \"csomagokban\", vagyis bájtonként tudjuk ezt megtenni. Hogy ez miért van így, azt most nem fogom itt fejtegetni Nos, ha megnézzük tehát, a boolean változó egy nagyon-nagyon pazarló megoldás. Hiszen 1 darab boolean változó 1 bájtot foglal, 4 darab boolean változó esetén ez már 4 bájt, ami megegyezik 1 integer változó méretével. Azonban, még a 4 boolean változóban 4 igaz/hamis érték kerülhet tárolásra, addig egy integerben 32 igaz/hamis értéket is eltárolhatunk.Bizony, 32 igaz/hamis érték. Hiszen 1 integer változó 32 bitbõl áll, ha minden bitet hasznosítunk, akkor a bitmûveletek felhasználásával 32 igaz/hamis értéket is eltárolhatunk. Összehasonlítva:32 darab boolean változó mérete 32 bájt.1 integer változó mérete: 4 bájt. Vagyis 32 boolean változó esetén nyolcad annyi memóriánkba kerül, ha egy darab integerben tároljuk el az értékeket. Persze 32 booleanra ritkán van szükség, de egy RP modban simán elõfordulhat. Gondoljunk csak bele... Van 32 boolean-tömbös változónk, ez a maximum slotszámot(1000) figyelembe véve: 32*1000 byte azaz 32000 byte ami ~31 kilobyte. Ha ezt egyetlen integer-tömbbel oldanánk meg: 4*1000 byte, azaz 4000 byte, ami ~3,9 kilobyte. Azért így nézve már nem annyira \"elhanyagolandó\" az a különbség szerintem =)Akár írhatnánk erre külön egy függvényt, mely az adott bit értékével tér vissza. Nem is bonyolult. Így már azok számára is használhatóak a bitmûveletek, akik nem értenek hozzá, és rengeteg memóriát takaríthatnak meg.Íme egy függvény az adatok kiolvasására:stock GetData(integer, bit){ return integer >> bit & 1; //vagy az 1 helyett akár 0x01 alakban is megadhatjuk, nincs jelentõsége} És a párja, adat írásra:stock SetData(&integer, bit, value){ integer |= value << bit; return 1;} Persze ezt még lehetne csinosítani, hogy pl. ne 0-tól hanem 1-tõl számoljuk a biteket, vagy hogy 31(vagy 32 ha 1-tõl számolunk) felett 0 legyen a return értéke, esetleg a value-re is rakhatnánk ellenõrzést, hogy 0-1-e az értéke, vagy más, de ilyenekkel most nem törõdtem.2.6.3 Több érték, egy integerbeNa ez az a rész, ami véleményem szerint már csak a nagyon megrögzött memóriafelszabadítók szerint nem lesz felesleges Amennyiben nincs szükségünk túl nagy számokra(pl. csak 0-15, vagy akár csak 0-7, de akár még a 0-255 is megfelel), akkor akár egy integerbe is tehetjük õket. Hogyan? Egyszerû. Csoportosítjuk a biteket. Tegyük fel, hogy maradván egy RP szervernél, el akarjuk tárolni ûbergigantikusan memóriakímélõ megoldással a játékos házának számát(ez legyen mondjuk 0-100 közötti szám), a játékos frakcióját(ez legyen 0-15 között), a játékos rangját(0-7 között), hogy a telefonja be van-e kapcsolva(0-1), a játékos szolgálati jármûvének id-jét(legyen 0-255 között), és az alapmunkáját(0-15 között). Ez is megoldható, egyetlen integerben, és ismételten 1000 slottal számolva máris megspóroltunk 4*4*1000+1*1000 byte-t(5 integer, és 1 boolean helyett csupán 1 integert használunk), ami ~16,6 kilobyte. Azért nem mindegy az, hogy 20,5 kb-ot vagy 4 kb-ot használunk szerintem... Ezt a következõ módszerrel valósíthatnánk meg:#define DATA_HOUSE 0#define DATA_FACTION 1#define DATA_RANK 2#define DATA_PHONESTATE 3#define DATA_DUTYCAR 4#define DATA_JOB 5stock GetData(playerinfo, data){ switch(data) { case DATA_HOUSE: return playerinfo & 127;//7 bit case DATA_FACTION: return playerinfo >> 7 & 15;//4 bit case DATA_RANK: return playerinfo >> 11 & 7;//3 bit case DATA_PHONESTATE: return playerinfo >> 14 & 1;//1 bit case DATA_DUTYCAR: return playerinfo >> 15 & 255;//8 bit case DATA_JOB: return playerinfo >> 23 & 15;//4 bit}return -1;}stock SetData(&playerinfo, data, value){ switch(data) { case DATA_HOUSE: { playerinfo = playerinfo & ~127 | value; }//7 bit (0-127 közötti maximálisan) case DATA_FACTION: { playerinfo = playerinfo & ~(15 << 7) | value << 7; }//4 bit (0-15 között) case DATA_RANK: { playerinfo = playerinfo & ~(7 << 11) | value << 11; }//3 bit (0-7 között) case DATA_PHONESTATE: { playerinfo = playerinfo & ~(1 << 14) | value << 14; }//1 bit (0-1) case DATA_DUTYCAR: { playerinfo = playerinfo & ~(255 << 15) | value << 15; }//8 bit (0-255 között) case DATA_JOB: { playerinfo = playerinfo & ~(15 << 23) | value << 23; }//4 bit (0-15 között) } return -1;}stock SetDatas(&playerinfo, house, faction, rank, phonestate, dutycar, job){playerinfo |= house | (faction << 7) | (rank << 11) | (phonestate << 14) | (dutycar << 15) | (job << 23);} Ez már azért elég morbid, ezt inkább csak érdekességképp mutattam, de éppenséggel ez is használható lenne, csak ehhez már kell egy kis õrültség 2.7 Második fejezet végeNos, láthattuk hogyan is mûködik mindazon sok \"marhaság\", amit az elsõ fejezetben elméletként átvettünk. Láthattuk hogyan mûködnek a jármûvek paneljének adataival babráló encode függvények, valamint megmutattam, hogyan írhatunk hozzájuk decode verziókat. Láthattunk még példát egyéb gyakorlati hasznosításra, kicsit megkoccoltuk a hexadecimális számok világát is, majd szóba került egy kis memóriatakarékosság. Végül mutattam egy összetettebb, elvetemült példát is, a bitmûveletek hasznosítására.3. Összegzés, végszó Úgy gondolom, hogy elég részletesen körbe jártuk a témát. Megnéztük a bitmûveletek elméleti, és gyakorlati oldalát is, láthattunk gyakorlati példákat. Nem kell elsõre megérteni ezt az egészet, sõt... Elegendõ ha egyszer elolvassuk, emésztjük kicsit, majd késõbb megint elõvesszük, akkor netán már kicsit többet is megértünk belõle. Nem egy kihagyhatatlan része a Pawno-nak, de azért van annak haszna, ha az ember jártas ebben a témában is, bár sokak szerint nem éri meg a sok fáradtságot, és idõt, mely a megértéséhez, begyakorlásához, használatának elsajátításához szükséges. Próbáltam mindent részletesen kifejteni. A bonyolultabb dolgokhoz sokat magyarázni, az egyszerûbbek esetén csak visszautalni az alapokhoz. Sok dolgot talán már túl is magyaráztam. Ezt a leírást nem 1 nap alatt készítettem el, kb. 1 hónapja írtam meg az elméleti oldalát, most az elmúlt pár napban pedig a gyakorlatot. Esténként 1-2 órát szántam rá csupán, és mivel ilyenkor már nem vagyok épp a toppon, elképzelhetõ, hogy néhány helyen helyesírási, vagy elvi hibát vétettem. Amennyiben valaki ilyet találna, kérem szóljon, és korrigálni fogom. Nos, azt hiszem nincs is más hátra, mint hogy sok szerencsét kívánjak a bitmûveletek elsajátításához, és megosszam a forrásokat. Remélem néhányan hasznosnak találják, eme igen hosszúra sikerült leírást.4. Források: Internet:WikipédiaSA:MP WikiSA:MP hivatalos fórumKönyv, e-book:Pohl László - A programozás alapjai(BME - Elektronikus Eszközök Tanszéke, Budapest, 2010)Siroki László - C programozás - Bitmûveletek jegyzeteˆ Anthony, 2012 - Minden jog fenntartva.A leírás egészben, vagy részleteiben szabadon terjeszthetõ, ameddig egyértelmûen fel van tüntetve az eredeti készítõ, valamint hogy honnan is származik. « Utoljára szerkesztve: 2012. augusztus 01. - 10:53:54 írta Anthony » Naplózva
Bin:~00000000000000000000000000000111=11111111111111111111111111111000Dec:~7=-8[/quote]Megint néhány példa: ~00000000000000000000000000000101 = 11111111111111111111111111111011~00000000000000000000000001110110= 11111111111111111111111110001010[/quote]Ehhez kapcsolódóan lenne itt egy fontos megjegyzendõ dolog. Aki nem érti, hogy miért -8 a megoldás, az nem olvasta el elég rendesen a \"Részletesebben a bitekrõl a Pawn-ban\" részt.1.4.5 Biteltolás (arithmetic shift, shiftelés) operátor(ok)A shift operátorok(<<, >>) a bitek tologatására használhatóak. A << balra tol, a >> jobbra. Bal oldalt található a szám, amit tologatni kívánunk, jobb oldalt pedig az, hogy mennyivel szeretnénk eltolni.1.4.5.1 Jobbra toló operátor(>>):Ez az operátor a szám bitjeit jobbra tolja a megadott mértékkel. Egy bittel való eltolás, 2-vel való osztásnak felel meg. Nézzünk egy példát: 00000000000000000000000000001000//8>>200000000000000000000000000000010//2[/quote]Láthatjuk, hogy semmi más nem történt, mint minden bitet 2-vel hátrébb mozgattunk. Az így \"megüresedett\" bitek helyére pedig 0-k kerültek. Lényegében az történt, hogy az utolsó 2 bit kiesett, az így megmaradt biteket 2-vel hátrébb csúsztattuk, majd az elsõ 2, így \"üres\" bit 0 értéket kapott.Nézzünk egy példát negatív számmal: 11111111111111111111111111110000//-8>>211111111111111111111111111111100//-2[/quote]Láthatjuk, hogy itt fontos szerepe van az MSB-nek. Ettõl függ ugyanis, hogy 0-k vagy 1-esek kerülnek a \"megüresedett\" bitek helyére. Fontos tehát megjegyezni, hogy a jobbra shiftelés megtartja a számok elõjelét, nem változtat azon!1.4.5.2 Balra toló operátor(<<):Ez az operátor a szám bitjeit balra tolja a megadott mértékkel. Egy bittel való eltolás, 2-vel való szorzásnak felel meg. Nézzünk egy példát: 00000000000000000000000000001000//8<<200000000000000000000000000100000//32[/quote]Itt megint könnyedén észrevehetõ mi is történt. Minden bitet fentebb \"toltunk\" 2-vel. Azok a bitek, amelyek ezáltal a 32. bit elé kerültek, kiesnek. Az így megüresedett bitek 0-kkal egészültek ki. Eddig nem túl bonyolult. Azonban figyeljünk oda, ha túl sokáig shiftelünk egy pozitív számot, akkor az utolsó érték, melyet fel tud venni a változónk (mielõtt még túlcsordulás miatt elérné a 0-t), az egy negatív szám!Nézzük mi történik negatív számok esetén: 11111111111111111111111111111000//-8<<211111111111111111111111111100000//-32[/quote]Láthatjuk hogy a balra shifteléskor a szám mindig 0-kkal egészítõdik ki, legyen az akár negatív, akár pozitív. Nem veszi figyelembe az MSB értékét, így ha túl sokat shiftelünk, akkor elõfordulhat, hogy a szám elveszti negatív értékét, és eléri a 0-t.1.4.6 Logikai biteltolás (logical shift, logikai shiftelés) operátorElõször is le kell tisztázni, hogy logikai shiftelésbõl csak egy létezik, jobbra logikai shiftelés(>>>). Sokban hasonlít a jobbra shiftelõ operátorra (>>), azonban van egy fontos különbség.Nézzük az elsõ példát: 00000000000000000000000000001000//8>>>200000000000000000000000000000010//2[/quote]Eddig nem igazán tapasztalhattunk semmi változást, minden pont ugyanúgy zajlott, mint a jobbra shiftelésnél(>>).Nézzük mi történik negatív szám esetén: 11111111111111111111111111111000//-8>>>200111111111111111111111111111110 //1073741822[/quote]Itt már láthatjuk a különbséget. Logikai shiftelés esetén az üres bitek helyére bekerülõ érték nem függ az MSB-tõl, hanem mindig 0. Emiatt soha nem kaphatunk eredményül negatív számot, logikai shiftelés esetén.1.5 Elsõ fejezet végeNos, ezennel átvettünk mindent, amit érdemes tudni a bitmûveletekrõl. Megmutattam az alapok, hogy hogyan épülnek fel a változók, valamint átvettük a fõ operátorokat. Eddig csak színtiszta elmélet volt. A következõ részben megmutatom, hogyan hasznosíthatjuk mindezt a gyakorlatban, vagyis mire is jó az a sok marhaság, amit eddig leírtam.2. Gyakorlati példák, érdekességek 2.1 Áttekintés, mikrõl is lesz még szóAz alábbiakban még pár dolgot szeretnék mutatni. Elõször is néhány \"függvénnyel\" fogok foglalkozni, majd mutatok még egy-két érdekességet. Konkrétan megmutatom, hogyan tudjuk egy jármû minden \"paneljének\" adatát kiíratni, részletesen(erre alapból nincs lehetõség, és a függvények is mind \"encode\" függvények, én viszont mutatok egy-egy decode \"verziót\" is.)Az alábbi 4 függvénnyel fogok foglalkozni: encode_tiresencode_panelsencode_doorsencode_lightsDe elõbb egy kis háttérinfó:A Pawnban a forrásban megadhatunk bináris számokat ugyanúgy, ahogyan Hex számokat is megadhatunk. Ehhez a \"0b\" prefixet kell használnunk. Tehát ha bináris számunk az 1011 és ezt akarjuk adni értékül egy változónak, azt így tehetjük meg: new var = 0b1011; 2.2 encode_tiresHáttér információ:A SA:MP-ban egy jármû abroncsainak adataid 4 bit tárolja. Az elsõ bit a jobb hátsó, a második bit a jobb elsõ, a harmadik bit a bal hátsó, a negyedik bit pedig a bal elsõ abroncsról tárol információt. Mivel 4 bitben eltárolhatóak az információk, így decimális formában 0-15 értékekkel is be lehet állítani a jármûvek abroncsait. 16-tól kezdve az értékek már ismétlõdni kezdenek, szóval a 16-nál az eredmény ugyanaz mint 0-nál, 17-nél ugyanaz mind 1-nél, és így tovább. Ez érthetõ, ha tudjuk hogyan épül fel a kettes számrendszer.(Ne felejtsük el, a biteket hátulról számozzuk!)Nos nézzük hogyan néz ki a kód: encode_tires(tire1, tire2, tire3, tire4){ return tire1 | tire2 << 1 | tire3 << 2 | tire4 << 3;} Láthatjuk, hogy bekérjük a 4 abroncs adatát(vagyis hogy ép[0], vagy kidurrant[1]), majd egy sima bitenkénti VAGY mûvelet és biteltolás kombinálásával visszatérünk az eredménnyel. A kódban a tire1 felel meg a jobb hátsó abroncsnak, a tire2 a jobb elsõ, a tire3 a bal hátsó, és a tire4 a bal elsõnek. Nézzünk egy konkrét példát. Tegyük fel, hogy én a hátsó 2 abroncsot ki akarom durrantani, még az elsõ kettõt épre akarom beállítani. Ez esetben így néz ki a dolog: encode_tire(1,0,1,0); Ha kiíratnánk konzolba a visszatérési értéket (%d-vel), 5-öt kapnánk. Miért is? Az 5 bináris alakja 0101. Hoppá, ha megnézzük ez pont fordítottja annak, amit mi megadtunk számjegyekként, csak már konkrét bináris számként. Nézzük hogyan is történhetett mindez, mit is csináltunk. A függvény a következõ mûveletet végzi: tire1 | tire2 << 1 | tire3 << 2 | tire4 << 3 Konyhanyelven úgy magyarázhatnánk el a dolgot, hogy ez a kód egymás mellé pakolgatja a megadott számokat, fordított sorrendben. Na de mi most nem a konyhanyelv miatt vagyunk ebben a leírásban, szóval nézzük a kódunkat. Lényegében ez futott le, a konkrét értékeinkkel nézve: 1 | 0 << 1 | 1 << 2 | 0 << 3 Bármily furcsa is, ez egy mûvelet, melyen szépen végig halad sorban a program. Elõször is írjuk át az egészet bináris alakba(az egyszerûség kedvéért most csak 4 bitet fogok kiírni): 0001 | 0000 << 1 | 0001 << 2 | 0000 << 3 Na erre se mondtuk volna rá a tutorial elsõ részének elolvasása elõtt, hogy ez egy mûveletsor. A rendszer elsõnek az eltolásokat végzi el (olyan ez mint a 2*3+2*4+2*5 mûveletben elõször a szorzásokat végezzük el, és csak utána adunk össze, ha nincs zárójel), és csak utána a bitenkénti VAGY mûveleteket, ezért mi is így fogunk tenni. Emlékezzünk, eltoláskor az \"üres\" bitek 0 értéket fognak felvenni. Végezzük el külön csak az eltolásokat: 0000 << 1 = 00000001 << 2 = 01000000 << 3 0000 Nos, ha ezeket elvégeztük, akkor nézzük, hogy is áll most a kódunk: 0001 | 0000 | 0100 | 0000 Négy azonos mûvelet van hátra, így hát a program innen más szépen sorban fog haladni. Tegyünk mi is így. Elsõ tehát a 0001 | 0000 mûvelet elvégzése. Emlékeztetõül, a bitenkénti VAGY mûvelet akkor ad 0-t eredményül, ha mind a kettõ érték 0, minden más esetben 1-et. Végezzük el külön az elsõ bitenkénti VAGY mûveletet: 0001 | 0000 = 0001 Nem volt bonyolult, nézzük tehát hogy áll akkor most a teljes mûveletünk: 0001 | 0100 | 0000 Haladunk szépen tovább, elvégezzük a 0001 | 0100 mûveletet. 0001 | 0100 = 0101 Nincs mit magyarázni rajta, egyszerû bitenkénti VAGY mûvelet volt ez is. Már csak ez van hátra: 0101 | 0000 Nos, ez sem lesz egy bonyolult mûvelet: 0101 | 0000 = 0101 Ezzel meg is kaptuk a végeredményt, ami nem más, mint a 0101 (tízes számrendszerben 5). Mivel azonban eléggé kusza lett így a számításunk, nézzük egyben hogyan is néz ki az egész, ha nem bontom ennyire részekre: 0100 | 0000 << 1 | 0001 << 2 | 0000 << 3v0001 | 0000 | 0100 | 0000v0001 | 0100 | 0000v0101 | 0000v0101 Nos, ezennel láthattuk a gyakorlatban is, hogyan mûködik a függvény. Kijelenthetjük tehát, hogy a függvény a számokat a sorrendjüknek megfelelõ számú bitbe helyezi. Mivel azonban a biteket hátulról számozzuk, így az 1,0,1,0 bemenetek végeredménye 0101 lesz. Így már nem is olyan bonyolult Na, ha már ilyen sokáig eljutottunk, akkor felhasználhatjuk ezt a kódot arra, hogy az alapjait figyelembe véve elkészítsük a decode változatot. Mivel 4 értékkel térünk vissza, ezért paraméterként kell majd 4 változó, melyeknek módosíthatjuk az értékét, illetve 1 paraméter pedig a dekódolandó szám. A függvényünk \"címsora\" tehát így néz ki: decode_tires(tires, &tire1, &tire2, &tire3, &tire4) Hogy példánk ne csak elmélet legyen, a tires változó legyen az elõbbi 0101 értékkel egyenlõ. Na akkor menjünk tovább. Bemeneti értékünk ugye a \"tires\" változóban lesz eltárolva, így ezzel fogjuk a mûveleteket végezni. Kezdjünk el gondolkozni. Hogyan is tudnánk ellenõrizni egy adatot. Legegyszerûbb ha a bitenkénti ÉS mûveletet alkalmazzuk. Miért is? Nos, a bitenkénti ÉS mûvelet ugye akkor ad eredményül 1-et, ha mind a 2 érték egy. Tehát ha min egy számot összehasonlítunk a 1-el(binárisan ugye 00000000000000000000000000000001, akkor az eredményünk lényegében arról fog tájékoztatni, hogy az elsõ bit 0, vagy 1. Nem is olyan bonyolult ez. Nézzük akkor az elsõ bitet. Arra emlékezhetünk még, hogy az elsõ bit a tire1, vagyis jobb hátsó abroncs adatait tárolja. Tehát: tire1 = tires & 1; Vagy konkrétan a példánk esetében: tire1 = 0101 & 0001 Mivel a bitenkénti ÉS mûvelet akkor ad 1-et eredményül, ha mind a kettõ bit 1, akkor már láthatjuk is, hogy miért elegendõ az elsõ bitet szemügyre venni. Mivel mind a 2 helyen az elsõ bit 1, ezért a tire1 változónk értéke is 1(0001) lesz. Egyszerû és nagyszerû, máris megkaptuk a jobb hátsó abroncs állapotát.Gondolkozzunk hát tovább. Nekünk most arra van szükségünk, hogy a 2. bit értékét megkapjuk. Hogyan kaphatjuk ezt meg? Legegyszerûbb megoldás, ha a tires változót jobbra shifteljük 1-el, majd ismételten bitenkénti ÉS mûveletet végzünk. Nézzük tehát, hogyan is nézne ez ki: tire2= tires >> 1 & 1; Vagy konkrétan a mi példánkban: tire2 = 0101 >> 1 & 0001vtire2 = 0010 & 0001 Ismételten bitenkénti ÉS mûvelet, és az eredményünk 0, mivel csak az egyik bit 1, a másik 0, így a bitenkénti ÉS mûvelet eredménye is 0. Ezen az elven megcsináljuk a maradék 2 abronccsal is: tire3= tires >> 2 & 1;tire4= tires >> 3 & 1; Vagy a mi esetünkben konkrét példánkkal: tire3 = 0101 >> 2 & 0001 // azaz: --> 0001 & 0001 = 1tire4 = 0101 >> 3 & 0001 // azaz: --> 0000 & 0001 = 0 A teljes kódunk tehát: decode_tires(tires, &tire1, &tire2, &tire3, &tire4){ tire1 = tires & 1; tire2 = tires >> 1 & 1; tire3 = tires >> 2 & 1; tire4 = tires >> 3 & 1;} A konkrét példánkban a 4 változónk értéke a függvény lefutása után: tire1: 1tire2: 0tire3: 1tire4: 0 Ha összehasonlítjuk, ezek pont azok az értékek, mint amiket az encode függvényben megadtunk, tehát úgy tûnik jól dolgoztunk 2.3 encode_lightsEz a rész lesz a legrövidebb. Az egész lámpás rész pontosan ugyanarra a sémára épül mint az abroncsok. Az adott bitek ugyanazon a \"helyen\" lévõ lámpa adatait szolgáltatják, ugyanúgy 0 az ép, és 1 a sérült lámpák állapot azonosítója, és ugyanúgy 1-1 bit tárolja az értékeket.Maga a kód: encode_lights(light1, light2, light3, light4){ return light1 | light2 << 1 | light3 << 2 | light4 << 3;} Láthatjuk hogy halál pontosan ugyanaz mint az encode_tires, csak más változónevekkel. Így hát a decode függvény is ugyan az lesz, csak a változóneveket kell módosítanunk: decode_lights(lights, &light1, &light2, &light3, &light4){ light1 = lights & 1; light2 = lights >> 1 & 1; light3 = lights >> 2 & 1; light4 = lights >> 3 & 1;} Ha megnézzük látjuk, hogy teljesen ugyan az a séma, ugyan az a felépítés, minden megegyezik, ezért nem is írtam le újból ugyanazt, amit egyszer már kifejtettem.2.4 encode_panelsHáttér információ:A SA:MP 7 panelrõl tárol adatokat. Egy panel adatait 4 bit írja le. Nos, ennél több infóra nekünk most nincs szükségünk, az elõzõ kód alapján ezen kód is megérthetõ, így az encode verzióhoz nem is nagyon szeretnék magyarázatot fûzni. A különbség az encode_tires-hez képest, hogy itt 4-essével shiftelünk, mivel 1 panel adatait 4 bit tárolja.Maga a kód: encode_panels(flp, frp, rlp, rrp, windshield, front_bumper, rear_bumper){ return flp | frp << 4 | rlp << 8 | rrp << 12 | windshield << 16 | front_bumper << 20 | rear_bumper << 24;} Nézzünk egy decode verziót, ehhez a kódhoz. Alapjaiban ugyanazon a vonalon kell elindulnunk, mint a decode_tires esetében. Itt most 7 visszatérési értékünk lesz, és 1 bemeneti érték. Tehát így néz ki a függvény header: decode_panels(panels, &flp, &frp, &rlp, &rrp, &windshield, &front_bumper, &rear_bumper) A folytatásban megint csak az elõbbi verziót kell alapul vennünk, azonban itt lesz egy kis különbség. Egyértelmû, hogy mivel 1 panel adatait 4 bit tárolja, így a 4 bitet nekünk egyben kell lekérni, és egyben kell vele visszatérnünk. Megint csak a bitenkénti ÉS mûveletet fogjuk alkalmazni, azonban mivel 4 bitrõl van szó, így egy olyan számmal kell összehasonlítanunk, mely bináris alakban 1111. Ez a szám nem más, mint a 15. Hogy miért ez kell?Ha egy bitsorozat tartalmát ki szeretnénk másolni egy másik változóba, akkor a bitenkénti ÉS mûveletre vna szükségünk. A bitenkénti ÉS mûvelet akkor ad 1-et eredményül, ha mindkét bit 1, ha valamelyik 0, akkor a visszatérés már 0 lesz. Ergó tehát, ha egy csupa 1-esekbõl álló bitsorozattal vetjük össze az eredeti bitsorozatunkat, akkor lényegében lemásoljuk a sorozatot, egy másik változóba, hisz eredményül mindig az eredeti sozatot fogjuk visszakapni. Íme néhány példa: 0101 & 1111 = 01011111 & 1111 = 11110000 & 1111 = 00001011 & 1111 = 1011 Láthatjuk tehát, hogy ha csupa 1-est tartalmazó bitsorozattal hasonlítjuk össze az eredeti számunkat, akkor lényegében egy másolatot készítünk. Mivel nekünk erre a 4 bitre van szükségünk a 32-bõl, így pontosan ezt kell tennünk, ezen 4 bitet lemásolni egy másik változóba, hogy késõbb ezt majd egy másik függvénnyel még jobban darabokra szedhessük.Íme tehát akkor a kódunk elsõ sora: flp = panels & 15; Mivel 4-esével vizsgáljuk a biteket, így lényegében annyit kell még változtatnunk az elõzõ kódon(decode_tires), hogy az 1-esével növekvõ shiftelések helyett 4-esével shifteljünk. Azaz a 2. panel adatainak lekérdezése: frp = panels >> 4 & 15; Innen pedig már csak mindig további 4 shiftelést el kell végeznünk, és szépen végig haladhatunk a panels változó bitjein, és megkaphatjuk minden panel adatait külön-külön. rlp = panels >> 8 & 15;rrp = panels >> 12 & 15;windshield = panels >> 16 & 15;front_bumper= panels >> 20 & 15;rear_bumper = panels >> 24 & 15; Ezennel minden panel adatait megkaptuk. Ezután már akár a decode_tires függvényt felhasználva szét lehet szedni az adatokat darabokra, de akár egy saját névvel, és más változó nevekkel ellátott másik függvényt is létre lehet hozni a decode_tires mintájára.Tehát az egész kód egyben: decode_panels(panels, &flp, &frp, &rlp, &rrp, &windshield, &front_bumper, &rear_bumper){ flp = panels & 15; frp = panels >> 4 & 15; rlp = panels >> 8 & 15; rrp = panels >> 12 & 15; windshield = panels >> 16 & 15; front_bumper= panels >> 20 & 15; rear_bumper = panels >> 24 & 15;} Igazából a függvény ugyanarra a sémára épül mint a decode_tires, csak annyi a különbség, hogy 4 bitenként kapjuk az adatokat, nem pedig egyesével, mivel itt minden panelnek 4 adatát tudjuk majd még lekérdezni. Nem tudom lehet-e még ehhez a részhez hozzáfûzni valamit, szóval szerintem ugorjunk. 2.5 encode_doorsHáttér információ:A SA:MP képes minden jármû 4 \"ajtajának\" állapotát tárolni. Ezek az \"ajtók\"(Doors-t hogy fordíthatnám jobban?) konkrétan a motorháztetõ, csomagtartó, vezetõoldali ajtó, és anyósülés oldali ajtó. A 2 hátsó ajtó állapotát jelenleg még nincs lehetõségünk lekérdezni.Amikor lekérdezzük egy jármû \"ajtajának\" adatait, azt a SA:MP 4 bájtként(32 bit) adja vissza. Ebbõl a 4 bájtból 1 bájt(8 bit) jut minden egyes objektumra. Vagyis minden egyes objektum adatait 8 bit tárolja. Vagyis tárolhatná...A SA:MP csak az elsõ 3 bitet használja, a további bitek üresek lesznek, nincs funkciójuk, talán majd késõbb lesz. Az elsõ bit azt tárolja, hogy az objektum zárt[0], vagy nyitott[1](nem az jelenti, hogy bezárt, hanem becsukott/kinyitott) állapotban van-e. A második bit azt adja meg, hogy az objektum ép[0] vagy sérült[1]. A 3. bit arról tájékoztat, hogy az adott objektum rajta van-e még a jármûvön[0] vagy már leesett[1]. A többi bit mint mondtam üres. Ez majd a decode függvénynél lesz fontos.Nézzük tehát a konkrét kódot: encode_doors(bonnet, boot, driver_door, passenger_door, behind_driver_door, behind_passenger_door){ #pragma unused behind_driver_door #pragma unused behind_passenger_door return bonnet | boot << 8 | driver_door << 16 | passenger_door << 24;} A 2 hátsó ajtóval nem foglalkozunk, mivel azok jelenleg még nem támogatottak ugyebár, de a kód készítõje gondolt a jövõre is, vagy fogalmam sincs miért adta meg ezt a két paramétert is, hisz ígyis-úgyis módosítani kell a kódon... Ha behoznak még 2 ajtót, akkor az 1 bájtos tárolással már 6 bájt lenne, de 1 változó maximum 4 bájt lehet, tehát a kódot úgyis módosítani kell, ha meg arra gondoltak, hogy legalább a paramétereket nem kell majd szegény scriptereknek minden sorban átírni, az megint hülyeség, hisz ki adna értéket olyan paraméternek, amely nincs használva?...Nos mindegy, nem a kritizálásért vagyunk most itt. Ha megnézzük, a kód 8-as shiftelést használ, mivel egy objektum adatait 8 bit tárolja. A mûködési elv ugyanaz, mint az elõzõ encode függvényeknél, így erre nem kívánok megint kitérni.Nézzük azonban hogyan írnánk ehhez egy decode függvényt. Mikor megkérdeztem errõl ismerõsömet, õ azt mondta, ugyanúgy mint az elõzõ, decode_panels függvénynél, csak 8-as shiftelést használunk, és 15 helyett 255-el végezzük a bitenkénti ÉS mûveletet. 255-el? minek?No erre is ki fogok térni késõbb, de haladjunk a szokásos módon. 1 bemeneti paraméterünk van, és 4 kimeneti, vagyis a header rész a következõ: decode_doors(doors, &bonnet, &boot, &driver_door, &passenger_door) Ezzel meg is vagyunk, itt nem látok különösebb magyarázni valót. Én most nem fogok törõdni a 2 nem létezõ ajtóval, ha egy jövõbeni verzióban netán azokat is behoznák mint lekérdezhetõ objektum, akkor ígyis-úgyis módosítani kell majd a kódot, addig meg úgysincs rá szükségünk.Nos, mint az eddigi decode függvényeknél, elsõ feladatunk egy shiftelés mentes bitenkénti ÉS mûvelet elvégzése. Az egyik megoldás a következõ: bonnet = doors & 255; Lehet így is. Azonban véleményem szerint felesleges itt egy ekkora számmal \"szenvednünk\". Miért használjunk ekkora számot, és ezáltal bonyolítsuk feleslegesen a kódunkat, ha nekünk a 8 bitbõl csak az elsõ 3 lesz ami adatot tárol? Bõven elegendõ egy olyan szám, amely bináris alakja 111. Ez nem más, mint a 7. Tehát a következõ megoldás ugyanúgy helyes: bonnet = doors & 7; A két kód pontosan ugyanazt az eredményt fogja visszaadni, semmi különbség a kettõ között. Csupán elsõ esetében nagy számmal dolgozunk, még második verzió esetén csak akkorával, amekkora szükséges.A kód többi részében csak 8-as shifteléssel végigcammogunk a doors változó többi bitjén is: boot = doors >> 8 & 7;driver_door = doors >> 16 & 7;passenger_door = doors >> 24 & 7; A teljes kód egyben: decode_doors(doors, &bonnet, &boot, &driver_door, &passenger_door){ bonnet = doors & 7; boot = doors >> 8 & 7; driver_door = doors >> 16 & 7; passenger_door = doors >> 24 & 7;} A kód pont ugyanazon az elven épül fel, mint az összes többi decode függvényünk, csak épp a benne használatos értékek az \"ajtó\" objectumok adatstruktúrájához vannak igazítva.2.6 Egyéb ötletek gyakorlati alkalmazásra, hasznosításraJoggal felmerülhet a kérdés, hogy ennyi? Emiatt rágtuk végig magunkat ezen néhány tízezer karakteren? Nem. Nem ennyi. A SA:MP-ban rengeteg felhasználási módja lehet még a bitmûveleteknek. Mutatok is néhány dolgot, mire jók még.2.6.1 RGBA->HEX; HEX->RGBA konvertálásokNos, bármily \"hihetetlen\", erre is használható. Miért is? Nos, 16-os számrendszerben 0-9, valamint A-F \"szánjegyek\" léteznek. A hexadecimális számjegy decimális értékben 0-15 lehet. Ha megnézzük bináris értékeket, 0-15 az 4 bittel leírható, hiszen a 15 az bináris alakban 1111. De ha józan paraszti ésszel belegondolunk is. 16 az 24. Így tehát 1 hexadecimális érték, az 4 bináris értékkel írható le. Ebbõl már sejthetõ, hogy itt is shiftelésre lesz szükségünk. Naná Mivel 4 bit ír le egy hexadecimális számjegyet, így 4-esével kell majd shiftelnünk.Mi is az az RGBA?RGBA: A színek olyan meghatározási módja, melyben a 3 alapszín([r]ed, [g]reen, lue) mellett egy 4. érték, az úgynevezett [a]lpha is szerepel, amely az áttetszõséget adja meg. A legtöbb program(pl. paint, PS), és pl. a css(külön függvénnyel!) is elfogadja ezen értékeket decimális számokként(0-255) is, a SA:MP-ban azonban hexadecimális alakban kell megadnunk. Bármily furcsa a következõ 4 sor pont ugyan azt jelenti:\"Normál\" RGB alak(pl. paint, photoshop): Piros: 0, Zöld: 255, Kék: 255CSS alak(függvénnyel): rgba(0, 255, 255, 1);Pawn alak(1): 0x00FFFFFFPawn alak(2): 16777215 Ó igen, a Pawn-ban decimális számokként is megadhatjuk a színeket, hisz a program mindent bináris számrendszerbe fog átszámolni. De akár bináris alakban is megadhattuk volna:Pawn alak(3): 0b111111111111111111111111 Az, hogy mi a Pawn-nak hexadecimális alakban szoktuk megadni, annak az egyetlen oka, hogy így a leginkább \"átlátható\", és ez egy amúgy is elfogadott módja (mármint a hexadecimális számmal való megadás) a színek leírásának a számítástechnikában.Nos, mivel 1 szín 0 és 255 közötti értéket tartalmaz a piros, zöld és kék színekbõl, ez hexadecimálisan 2 számjeggyel leírható: 00-FF. Az FF értéke 255, aki nem hiszi, számoljon utána (162-1)2 hexadecimális számjegy az pedig 8 bináris számjeggyel leírható(00000000-11111111). Így már láthatjuk, hogy 8-as shiftelésre lesz szükségünk. Másra nincs is szükségünk, az RGBA->HEX konverter elkészítéséhez.stock RGBAToHex( r, g, b, a ){return a | b << 8 | g << 16 | r << 24;} Az, hogy miért fordított sorrendben kellett megadnunk az értékeket, az megint csak azzal magyarázható, hogy a biteket hétköznapi értelemben véve visszafelé számozzuk.Egy ellentét, azaz HexToRGBA függvény megírása sem túl bonyolult, ugyan ezt kell csinálnunk, csak visszafelé.stock HexToRGBA( colour, &r, &g, &b, &a ){a = colour & 0xFF;b = (colour >> 8 ) & 0xFF;g = (colour >> 16 ) & 0xFF;r = (colour >> 24 ) & 0xFF;} A 0xFF-et természetesen decimális értékkel is megadhatjuk(255).No akkor lehet ugye RGBA-t ARGB-be is konvertálni, az így néz ki:RGBAToARGBHEX(r,g,b,a){return (r & 0xff) << 24 | (g & 0xff) << 16 | (b & 0xff) << 8 | a & 0xff;} Ez máris egy újabb felhasználási módja a bitmûveleteknek. De, mutatok még 2.6.2 Boolen változók helyett IntegerAz al-al-alcímet elolvasva joggal felmerülhet a kérdés, hogy ez meg hogy jön ide. Nos ez úgy jön ide, hogy most egy kis memóriatakarékosságról fogok itt regélni. Adhattam volna azt is címnek, hogy miért ne használjunk boolean változókat, ha legalább 5 igaz/hamis értéket el akarunk tárolni.Nos akkor egy kis memórialecke. Azt már tisztáztuk, hogy az integer változók 32 bitesek, vagyis 4 bájtot foglalnak le a memóriában maguknak. A boolean típusú változókról azonban még nem volt szó. Mivel 1 értéket tárolnak csak, az is csak igaz vagy hamis lehet, így joggal gondolhatnánk, hogy 1 bit a mérete. Azonban nem...1 boolean változó 8 bitet, azaz 1 bájtot foglal le magának a memóriában. Hogy ez miért így van, az a számítógépek memóriahasználatával magyarázható. Manapság nincs mód arra, hogy a memóriában egyetlen bitet módosítsunk, csak 8-as \"csomagokban\", vagyis bájtonként tudjuk ezt megtenni. Hogy ez miért van így, azt most nem fogom itt fejtegetni Nos, ha megnézzük tehát, a boolean változó egy nagyon-nagyon pazarló megoldás. Hiszen 1 darab boolean változó 1 bájtot foglal, 4 darab boolean változó esetén ez már 4 bájt, ami megegyezik 1 integer változó méretével. Azonban, még a 4 boolean változóban 4 igaz/hamis érték kerülhet tárolásra, addig egy integerben 32 igaz/hamis értéket is eltárolhatunk.Bizony, 32 igaz/hamis érték. Hiszen 1 integer változó 32 bitbõl áll, ha minden bitet hasznosítunk, akkor a bitmûveletek felhasználásával 32 igaz/hamis értéket is eltárolhatunk. Összehasonlítva:32 darab boolean változó mérete 32 bájt.1 integer változó mérete: 4 bájt. Vagyis 32 boolean változó esetén nyolcad annyi memóriánkba kerül, ha egy darab integerben tároljuk el az értékeket. Persze 32 booleanra ritkán van szükség, de egy RP modban simán elõfordulhat. Gondoljunk csak bele... Van 32 boolean-tömbös változónk, ez a maximum slotszámot(1000) figyelembe véve: 32*1000 byte azaz 32000 byte ami ~31 kilobyte. Ha ezt egyetlen integer-tömbbel oldanánk meg: 4*1000 byte, azaz 4000 byte, ami ~3,9 kilobyte. Azért így nézve már nem annyira \"elhanyagolandó\" az a különbség szerintem =)Akár írhatnánk erre külön egy függvényt, mely az adott bit értékével tér vissza. Nem is bonyolult. Így már azok számára is használhatóak a bitmûveletek, akik nem értenek hozzá, és rengeteg memóriát takaríthatnak meg.Íme egy függvény az adatok kiolvasására:stock GetData(integer, bit){ return integer >> bit & 1; //vagy az 1 helyett akár 0x01 alakban is megadhatjuk, nincs jelentõsége} És a párja, adat írásra:stock SetData(&integer, bit, value){ integer |= value << bit; return 1;} Persze ezt még lehetne csinosítani, hogy pl. ne 0-tól hanem 1-tõl számoljuk a biteket, vagy hogy 31(vagy 32 ha 1-tõl számolunk) felett 0 legyen a return értéke, esetleg a value-re is rakhatnánk ellenõrzést, hogy 0-1-e az értéke, vagy más, de ilyenekkel most nem törõdtem.2.6.3 Több érték, egy integerbeNa ez az a rész, ami véleményem szerint már csak a nagyon megrögzött memóriafelszabadítók szerint nem lesz felesleges Amennyiben nincs szükségünk túl nagy számokra(pl. csak 0-15, vagy akár csak 0-7, de akár még a 0-255 is megfelel), akkor akár egy integerbe is tehetjük õket. Hogyan? Egyszerû. Csoportosítjuk a biteket. Tegyük fel, hogy maradván egy RP szervernél, el akarjuk tárolni ûbergigantikusan memóriakímélõ megoldással a játékos házának számát(ez legyen mondjuk 0-100 közötti szám), a játékos frakcióját(ez legyen 0-15 között), a játékos rangját(0-7 között), hogy a telefonja be van-e kapcsolva(0-1), a játékos szolgálati jármûvének id-jét(legyen 0-255 között), és az alapmunkáját(0-15 között). Ez is megoldható, egyetlen integerben, és ismételten 1000 slottal számolva máris megspóroltunk 4*4*1000+1*1000 byte-t(5 integer, és 1 boolean helyett csupán 1 integert használunk), ami ~16,6 kilobyte. Azért nem mindegy az, hogy 20,5 kb-ot vagy 4 kb-ot használunk szerintem... Ezt a következõ módszerrel valósíthatnánk meg:#define DATA_HOUSE 0#define DATA_FACTION 1#define DATA_RANK 2#define DATA_PHONESTATE 3#define DATA_DUTYCAR 4#define DATA_JOB 5stock GetData(playerinfo, data){ switch(data) { case DATA_HOUSE: return playerinfo & 127;//7 bit case DATA_FACTION: return playerinfo >> 7 & 15;//4 bit case DATA_RANK: return playerinfo >> 11 & 7;//3 bit case DATA_PHONESTATE: return playerinfo >> 14 & 1;//1 bit case DATA_DUTYCAR: return playerinfo >> 15 & 255;//8 bit case DATA_JOB: return playerinfo >> 23 & 15;//4 bit}return -1;}stock SetData(&playerinfo, data, value){ switch(data) { case DATA_HOUSE: { playerinfo = playerinfo & ~127 | value; }//7 bit (0-127 közötti maximálisan) case DATA_FACTION: { playerinfo = playerinfo & ~(15 << 7) | value << 7; }//4 bit (0-15 között) case DATA_RANK: { playerinfo = playerinfo & ~(7 << 11) | value << 11; }//3 bit (0-7 között) case DATA_PHONESTATE: { playerinfo = playerinfo & ~(1 << 14) | value << 14; }//1 bit (0-1) case DATA_DUTYCAR: { playerinfo = playerinfo & ~(255 << 15) | value << 15; }//8 bit (0-255 között) case DATA_JOB: { playerinfo = playerinfo & ~(15 << 23) | value << 23; }//4 bit (0-15 között) } return -1;}stock SetDatas(&playerinfo, house, faction, rank, phonestate, dutycar, job){playerinfo |= house | (faction << 7) | (rank << 11) | (phonestate << 14) | (dutycar << 15) | (job << 23);} Ez már azért elég morbid, ezt inkább csak érdekességképp mutattam, de éppenséggel ez is használható lenne, csak ehhez már kell egy kis õrültség 2.7 Második fejezet végeNos, láthattuk hogyan is mûködik mindazon sok \"marhaság\", amit az elsõ fejezetben elméletként átvettünk. Láthattuk hogyan mûködnek a jármûvek paneljének adataival babráló encode függvények, valamint megmutattam, hogyan írhatunk hozzájuk decode verziókat. Láthattunk még példát egyéb gyakorlati hasznosításra, kicsit megkoccoltuk a hexadecimális számok világát is, majd szóba került egy kis memóriatakarékosság. Végül mutattam egy összetettebb, elvetemült példát is, a bitmûveletek hasznosítására.3. Összegzés, végszó Úgy gondolom, hogy elég részletesen körbe jártuk a témát. Megnéztük a bitmûveletek elméleti, és gyakorlati oldalát is, láthattunk gyakorlati példákat. Nem kell elsõre megérteni ezt az egészet, sõt... Elegendõ ha egyszer elolvassuk, emésztjük kicsit, majd késõbb megint elõvesszük, akkor netán már kicsit többet is megértünk belõle. Nem egy kihagyhatatlan része a Pawno-nak, de azért van annak haszna, ha az ember jártas ebben a témában is, bár sokak szerint nem éri meg a sok fáradtságot, és idõt, mely a megértéséhez, begyakorlásához, használatának elsajátításához szükséges. Próbáltam mindent részletesen kifejteni. A bonyolultabb dolgokhoz sokat magyarázni, az egyszerûbbek esetén csak visszautalni az alapokhoz. Sok dolgot talán már túl is magyaráztam. Ezt a leírást nem 1 nap alatt készítettem el, kb. 1 hónapja írtam meg az elméleti oldalát, most az elmúlt pár napban pedig a gyakorlatot. Esténként 1-2 órát szántam rá csupán, és mivel ilyenkor már nem vagyok épp a toppon, elképzelhetõ, hogy néhány helyen helyesírási, vagy elvi hibát vétettem. Amennyiben valaki ilyet találna, kérem szóljon, és korrigálni fogom. Nos, azt hiszem nincs is más hátra, mint hogy sok szerencsét kívánjak a bitmûveletek elsajátításához, és megosszam a forrásokat. Remélem néhányan hasznosnak találják, eme igen hosszúra sikerült leírást.4. Források: Internet:WikipédiaSA:MP WikiSA:MP hivatalos fórumKönyv, e-book:Pohl László - A programozás alapjai(BME - Elektronikus Eszközök Tanszéke, Budapest, 2010)Siroki László - C programozás - Bitmûveletek jegyzeteˆ Anthony, 2012 - Minden jog fenntartva.A leírás egészben, vagy részleteiben szabadon terjeszthetõ, ameddig egyértelmûen fel van tüntetve az eredeti készítõ, valamint hogy honnan is származik. « Utoljára szerkesztve: 2012. augusztus 01. - 10:53:54 írta Anthony » Naplózva
~00000000000000000000000000000101 = 11111111111111111111111111111011~00000000000000000000000001110110= 11111111111111111111111110001010[/quote]Ehhez kapcsolódóan lenne itt egy fontos megjegyzendõ dolog. Aki nem érti, hogy miért -8 a megoldás, az nem olvasta el elég rendesen a \"Részletesebben a bitekrõl a Pawn-ban\" részt.1.4.5 Biteltolás (arithmetic shift, shiftelés) operátor(ok)A shift operátorok(<<, >>) a bitek tologatására használhatóak. A << balra tol, a >> jobbra. Bal oldalt található a szám, amit tologatni kívánunk, jobb oldalt pedig az, hogy mennyivel szeretnénk eltolni.1.4.5.1 Jobbra toló operátor(>>):Ez az operátor a szám bitjeit jobbra tolja a megadott mértékkel. Egy bittel való eltolás, 2-vel való osztásnak felel meg. Nézzünk egy példát: 00000000000000000000000000001000//8>>200000000000000000000000000000010//2[/quote]Láthatjuk, hogy semmi más nem történt, mint minden bitet 2-vel hátrébb mozgattunk. Az így \"megüresedett\" bitek helyére pedig 0-k kerültek. Lényegében az történt, hogy az utolsó 2 bit kiesett, az így megmaradt biteket 2-vel hátrébb csúsztattuk, majd az elsõ 2, így \"üres\" bit 0 értéket kapott.Nézzünk egy példát negatív számmal: 11111111111111111111111111110000//-8>>211111111111111111111111111111100//-2[/quote]Láthatjuk, hogy itt fontos szerepe van az MSB-nek. Ettõl függ ugyanis, hogy 0-k vagy 1-esek kerülnek a \"megüresedett\" bitek helyére. Fontos tehát megjegyezni, hogy a jobbra shiftelés megtartja a számok elõjelét, nem változtat azon!1.4.5.2 Balra toló operátor(<<):Ez az operátor a szám bitjeit balra tolja a megadott mértékkel. Egy bittel való eltolás, 2-vel való szorzásnak felel meg. Nézzünk egy példát: 00000000000000000000000000001000//8<<200000000000000000000000000100000//32[/quote]Itt megint könnyedén észrevehetõ mi is történt. Minden bitet fentebb \"toltunk\" 2-vel. Azok a bitek, amelyek ezáltal a 32. bit elé kerültek, kiesnek. Az így megüresedett bitek 0-kkal egészültek ki. Eddig nem túl bonyolult. Azonban figyeljünk oda, ha túl sokáig shiftelünk egy pozitív számot, akkor az utolsó érték, melyet fel tud venni a változónk (mielõtt még túlcsordulás miatt elérné a 0-t), az egy negatív szám!Nézzük mi történik negatív számok esetén: 11111111111111111111111111111000//-8<<211111111111111111111111111100000//-32[/quote]Láthatjuk hogy a balra shifteléskor a szám mindig 0-kkal egészítõdik ki, legyen az akár negatív, akár pozitív. Nem veszi figyelembe az MSB értékét, így ha túl sokat shiftelünk, akkor elõfordulhat, hogy a szám elveszti negatív értékét, és eléri a 0-t.1.4.6 Logikai biteltolás (logical shift, logikai shiftelés) operátorElõször is le kell tisztázni, hogy logikai shiftelésbõl csak egy létezik, jobbra logikai shiftelés(>>>). Sokban hasonlít a jobbra shiftelõ operátorra (>>), azonban van egy fontos különbség.Nézzük az elsõ példát: 00000000000000000000000000001000//8>>>200000000000000000000000000000010//2[/quote]Eddig nem igazán tapasztalhattunk semmi változást, minden pont ugyanúgy zajlott, mint a jobbra shiftelésnél(>>).Nézzük mi történik negatív szám esetén: 11111111111111111111111111111000//-8>>>200111111111111111111111111111110 //1073741822[/quote]Itt már láthatjuk a különbséget. Logikai shiftelés esetén az üres bitek helyére bekerülõ érték nem függ az MSB-tõl, hanem mindig 0. Emiatt soha nem kaphatunk eredményül negatív számot, logikai shiftelés esetén.1.5 Elsõ fejezet végeNos, ezennel átvettünk mindent, amit érdemes tudni a bitmûveletekrõl. Megmutattam az alapok, hogy hogyan épülnek fel a változók, valamint átvettük a fõ operátorokat. Eddig csak színtiszta elmélet volt. A következõ részben megmutatom, hogyan hasznosíthatjuk mindezt a gyakorlatban, vagyis mire is jó az a sok marhaság, amit eddig leírtam.2. Gyakorlati példák, érdekességek 2.1 Áttekintés, mikrõl is lesz még szóAz alábbiakban még pár dolgot szeretnék mutatni. Elõször is néhány \"függvénnyel\" fogok foglalkozni, majd mutatok még egy-két érdekességet. Konkrétan megmutatom, hogyan tudjuk egy jármû minden \"paneljének\" adatát kiíratni, részletesen(erre alapból nincs lehetõség, és a függvények is mind \"encode\" függvények, én viszont mutatok egy-egy decode \"verziót\" is.)Az alábbi 4 függvénnyel fogok foglalkozni: encode_tiresencode_panelsencode_doorsencode_lightsDe elõbb egy kis háttérinfó:A Pawnban a forrásban megadhatunk bináris számokat ugyanúgy, ahogyan Hex számokat is megadhatunk. Ehhez a \"0b\" prefixet kell használnunk. Tehát ha bináris számunk az 1011 és ezt akarjuk adni értékül egy változónak, azt így tehetjük meg: new var = 0b1011; 2.2 encode_tiresHáttér információ:A SA:MP-ban egy jármû abroncsainak adataid 4 bit tárolja. Az elsõ bit a jobb hátsó, a második bit a jobb elsõ, a harmadik bit a bal hátsó, a negyedik bit pedig a bal elsõ abroncsról tárol információt. Mivel 4 bitben eltárolhatóak az információk, így decimális formában 0-15 értékekkel is be lehet állítani a jármûvek abroncsait. 16-tól kezdve az értékek már ismétlõdni kezdenek, szóval a 16-nál az eredmény ugyanaz mint 0-nál, 17-nél ugyanaz mind 1-nél, és így tovább. Ez érthetõ, ha tudjuk hogyan épül fel a kettes számrendszer.(Ne felejtsük el, a biteket hátulról számozzuk!)Nos nézzük hogyan néz ki a kód: encode_tires(tire1, tire2, tire3, tire4){ return tire1 | tire2 << 1 | tire3 << 2 | tire4 << 3;} Láthatjuk, hogy bekérjük a 4 abroncs adatát(vagyis hogy ép[0], vagy kidurrant[1]), majd egy sima bitenkénti VAGY mûvelet és biteltolás kombinálásával visszatérünk az eredménnyel. A kódban a tire1 felel meg a jobb hátsó abroncsnak, a tire2 a jobb elsõ, a tire3 a bal hátsó, és a tire4 a bal elsõnek. Nézzünk egy konkrét példát. Tegyük fel, hogy én a hátsó 2 abroncsot ki akarom durrantani, még az elsõ kettõt épre akarom beállítani. Ez esetben így néz ki a dolog: encode_tire(1,0,1,0); Ha kiíratnánk konzolba a visszatérési értéket (%d-vel), 5-öt kapnánk. Miért is? Az 5 bináris alakja 0101. Hoppá, ha megnézzük ez pont fordítottja annak, amit mi megadtunk számjegyekként, csak már konkrét bináris számként. Nézzük hogyan is történhetett mindez, mit is csináltunk. A függvény a következõ mûveletet végzi: tire1 | tire2 << 1 | tire3 << 2 | tire4 << 3 Konyhanyelven úgy magyarázhatnánk el a dolgot, hogy ez a kód egymás mellé pakolgatja a megadott számokat, fordított sorrendben. Na de mi most nem a konyhanyelv miatt vagyunk ebben a leírásban, szóval nézzük a kódunkat. Lényegében ez futott le, a konkrét értékeinkkel nézve: 1 | 0 << 1 | 1 << 2 | 0 << 3 Bármily furcsa is, ez egy mûvelet, melyen szépen végig halad sorban a program. Elõször is írjuk át az egészet bináris alakba(az egyszerûség kedvéért most csak 4 bitet fogok kiírni): 0001 | 0000 << 1 | 0001 << 2 | 0000 << 3 Na erre se mondtuk volna rá a tutorial elsõ részének elolvasása elõtt, hogy ez egy mûveletsor. A rendszer elsõnek az eltolásokat végzi el (olyan ez mint a 2*3+2*4+2*5 mûveletben elõször a szorzásokat végezzük el, és csak utána adunk össze, ha nincs zárójel), és csak utána a bitenkénti VAGY mûveleteket, ezért mi is így fogunk tenni. Emlékezzünk, eltoláskor az \"üres\" bitek 0 értéket fognak felvenni. Végezzük el külön csak az eltolásokat: 0000 << 1 = 00000001 << 2 = 01000000 << 3 0000 Nos, ha ezeket elvégeztük, akkor nézzük, hogy is áll most a kódunk: 0001 | 0000 | 0100 | 0000 Négy azonos mûvelet van hátra, így hát a program innen más szépen sorban fog haladni. Tegyünk mi is így. Elsõ tehát a 0001 | 0000 mûvelet elvégzése. Emlékeztetõül, a bitenkénti VAGY mûvelet akkor ad 0-t eredményül, ha mind a kettõ érték 0, minden más esetben 1-et. Végezzük el külön az elsõ bitenkénti VAGY mûveletet: 0001 | 0000 = 0001 Nem volt bonyolult, nézzük tehát hogy áll akkor most a teljes mûveletünk: 0001 | 0100 | 0000 Haladunk szépen tovább, elvégezzük a 0001 | 0100 mûveletet. 0001 | 0100 = 0101 Nincs mit magyarázni rajta, egyszerû bitenkénti VAGY mûvelet volt ez is. Már csak ez van hátra: 0101 | 0000 Nos, ez sem lesz egy bonyolult mûvelet: 0101 | 0000 = 0101 Ezzel meg is kaptuk a végeredményt, ami nem más, mint a 0101 (tízes számrendszerben 5). Mivel azonban eléggé kusza lett így a számításunk, nézzük egyben hogyan is néz ki az egész, ha nem bontom ennyire részekre: 0100 | 0000 << 1 | 0001 << 2 | 0000 << 3v0001 | 0000 | 0100 | 0000v0001 | 0100 | 0000v0101 | 0000v0101 Nos, ezennel láthattuk a gyakorlatban is, hogyan mûködik a függvény. Kijelenthetjük tehát, hogy a függvény a számokat a sorrendjüknek megfelelõ számú bitbe helyezi. Mivel azonban a biteket hátulról számozzuk, így az 1,0,1,0 bemenetek végeredménye 0101 lesz. Így már nem is olyan bonyolult Na, ha már ilyen sokáig eljutottunk, akkor felhasználhatjuk ezt a kódot arra, hogy az alapjait figyelembe véve elkészítsük a decode változatot. Mivel 4 értékkel térünk vissza, ezért paraméterként kell majd 4 változó, melyeknek módosíthatjuk az értékét, illetve 1 paraméter pedig a dekódolandó szám. A függvényünk \"címsora\" tehát így néz ki: decode_tires(tires, &tire1, &tire2, &tire3, &tire4) Hogy példánk ne csak elmélet legyen, a tires változó legyen az elõbbi 0101 értékkel egyenlõ. Na akkor menjünk tovább. Bemeneti értékünk ugye a \"tires\" változóban lesz eltárolva, így ezzel fogjuk a mûveleteket végezni. Kezdjünk el gondolkozni. Hogyan is tudnánk ellenõrizni egy adatot. Legegyszerûbb ha a bitenkénti ÉS mûveletet alkalmazzuk. Miért is? Nos, a bitenkénti ÉS mûvelet ugye akkor ad eredményül 1-et, ha mind a 2 érték egy. Tehát ha min egy számot összehasonlítunk a 1-el(binárisan ugye 00000000000000000000000000000001, akkor az eredményünk lényegében arról fog tájékoztatni, hogy az elsõ bit 0, vagy 1. Nem is olyan bonyolult ez. Nézzük akkor az elsõ bitet. Arra emlékezhetünk még, hogy az elsõ bit a tire1, vagyis jobb hátsó abroncs adatait tárolja. Tehát: tire1 = tires & 1; Vagy konkrétan a példánk esetében: tire1 = 0101 & 0001 Mivel a bitenkénti ÉS mûvelet akkor ad 1-et eredményül, ha mind a kettõ bit 1, akkor már láthatjuk is, hogy miért elegendõ az elsõ bitet szemügyre venni. Mivel mind a 2 helyen az elsõ bit 1, ezért a tire1 változónk értéke is 1(0001) lesz. Egyszerû és nagyszerû, máris megkaptuk a jobb hátsó abroncs állapotát.Gondolkozzunk hát tovább. Nekünk most arra van szükségünk, hogy a 2. bit értékét megkapjuk. Hogyan kaphatjuk ezt meg? Legegyszerûbb megoldás, ha a tires változót jobbra shifteljük 1-el, majd ismételten bitenkénti ÉS mûveletet végzünk. Nézzük tehát, hogyan is nézne ez ki: tire2= tires >> 1 & 1; Vagy konkrétan a mi példánkban: tire2 = 0101 >> 1 & 0001vtire2 = 0010 & 0001 Ismételten bitenkénti ÉS mûvelet, és az eredményünk 0, mivel csak az egyik bit 1, a másik 0, így a bitenkénti ÉS mûvelet eredménye is 0. Ezen az elven megcsináljuk a maradék 2 abronccsal is: tire3= tires >> 2 & 1;tire4= tires >> 3 & 1; Vagy a mi esetünkben konkrét példánkkal: tire3 = 0101 >> 2 & 0001 // azaz: --> 0001 & 0001 = 1tire4 = 0101 >> 3 & 0001 // azaz: --> 0000 & 0001 = 0 A teljes kódunk tehát: decode_tires(tires, &tire1, &tire2, &tire3, &tire4){ tire1 = tires & 1; tire2 = tires >> 1 & 1; tire3 = tires >> 2 & 1; tire4 = tires >> 3 & 1;} A konkrét példánkban a 4 változónk értéke a függvény lefutása után: tire1: 1tire2: 0tire3: 1tire4: 0 Ha összehasonlítjuk, ezek pont azok az értékek, mint amiket az encode függvényben megadtunk, tehát úgy tûnik jól dolgoztunk 2.3 encode_lightsEz a rész lesz a legrövidebb. Az egész lámpás rész pontosan ugyanarra a sémára épül mint az abroncsok. Az adott bitek ugyanazon a \"helyen\" lévõ lámpa adatait szolgáltatják, ugyanúgy 0 az ép, és 1 a sérült lámpák állapot azonosítója, és ugyanúgy 1-1 bit tárolja az értékeket.Maga a kód: encode_lights(light1, light2, light3, light4){ return light1 | light2 << 1 | light3 << 2 | light4 << 3;} Láthatjuk hogy halál pontosan ugyanaz mint az encode_tires, csak más változónevekkel. Így hát a decode függvény is ugyan az lesz, csak a változóneveket kell módosítanunk: decode_lights(lights, &light1, &light2, &light3, &light4){ light1 = lights & 1; light2 = lights >> 1 & 1; light3 = lights >> 2 & 1; light4 = lights >> 3 & 1;} Ha megnézzük látjuk, hogy teljesen ugyan az a séma, ugyan az a felépítés, minden megegyezik, ezért nem is írtam le újból ugyanazt, amit egyszer már kifejtettem.2.4 encode_panelsHáttér információ:A SA:MP 7 panelrõl tárol adatokat. Egy panel adatait 4 bit írja le. Nos, ennél több infóra nekünk most nincs szükségünk, az elõzõ kód alapján ezen kód is megérthetõ, így az encode verzióhoz nem is nagyon szeretnék magyarázatot fûzni. A különbség az encode_tires-hez képest, hogy itt 4-essével shiftelünk, mivel 1 panel adatait 4 bit tárolja.Maga a kód: encode_panels(flp, frp, rlp, rrp, windshield, front_bumper, rear_bumper){ return flp | frp << 4 | rlp << 8 | rrp << 12 | windshield << 16 | front_bumper << 20 | rear_bumper << 24;} Nézzünk egy decode verziót, ehhez a kódhoz. Alapjaiban ugyanazon a vonalon kell elindulnunk, mint a decode_tires esetében. Itt most 7 visszatérési értékünk lesz, és 1 bemeneti érték. Tehát így néz ki a függvény header: decode_panels(panels, &flp, &frp, &rlp, &rrp, &windshield, &front_bumper, &rear_bumper) A folytatásban megint csak az elõbbi verziót kell alapul vennünk, azonban itt lesz egy kis különbség. Egyértelmû, hogy mivel 1 panel adatait 4 bit tárolja, így a 4 bitet nekünk egyben kell lekérni, és egyben kell vele visszatérnünk. Megint csak a bitenkénti ÉS mûveletet fogjuk alkalmazni, azonban mivel 4 bitrõl van szó, így egy olyan számmal kell összehasonlítanunk, mely bináris alakban 1111. Ez a szám nem más, mint a 15. Hogy miért ez kell?Ha egy bitsorozat tartalmát ki szeretnénk másolni egy másik változóba, akkor a bitenkénti ÉS mûveletre vna szükségünk. A bitenkénti ÉS mûvelet akkor ad 1-et eredményül, ha mindkét bit 1, ha valamelyik 0, akkor a visszatérés már 0 lesz. Ergó tehát, ha egy csupa 1-esekbõl álló bitsorozattal vetjük össze az eredeti bitsorozatunkat, akkor lényegében lemásoljuk a sorozatot, egy másik változóba, hisz eredményül mindig az eredeti sozatot fogjuk visszakapni. Íme néhány példa: 0101 & 1111 = 01011111 & 1111 = 11110000 & 1111 = 00001011 & 1111 = 1011 Láthatjuk tehát, hogy ha csupa 1-est tartalmazó bitsorozattal hasonlítjuk össze az eredeti számunkat, akkor lényegében egy másolatot készítünk. Mivel nekünk erre a 4 bitre van szükségünk a 32-bõl, így pontosan ezt kell tennünk, ezen 4 bitet lemásolni egy másik változóba, hogy késõbb ezt majd egy másik függvénnyel még jobban darabokra szedhessük.Íme tehát akkor a kódunk elsõ sora: flp = panels & 15; Mivel 4-esével vizsgáljuk a biteket, így lényegében annyit kell még változtatnunk az elõzõ kódon(decode_tires), hogy az 1-esével növekvõ shiftelések helyett 4-esével shifteljünk. Azaz a 2. panel adatainak lekérdezése: frp = panels >> 4 & 15; Innen pedig már csak mindig további 4 shiftelést el kell végeznünk, és szépen végig haladhatunk a panels változó bitjein, és megkaphatjuk minden panel adatait külön-külön. rlp = panels >> 8 & 15;rrp = panels >> 12 & 15;windshield = panels >> 16 & 15;front_bumper= panels >> 20 & 15;rear_bumper = panels >> 24 & 15; Ezennel minden panel adatait megkaptuk. Ezután már akár a decode_tires függvényt felhasználva szét lehet szedni az adatokat darabokra, de akár egy saját névvel, és más változó nevekkel ellátott másik függvényt is létre lehet hozni a decode_tires mintájára.Tehát az egész kód egyben: decode_panels(panels, &flp, &frp, &rlp, &rrp, &windshield, &front_bumper, &rear_bumper){ flp = panels & 15; frp = panels >> 4 & 15; rlp = panels >> 8 & 15; rrp = panels >> 12 & 15; windshield = panels >> 16 & 15; front_bumper= panels >> 20 & 15; rear_bumper = panels >> 24 & 15;} Igazából a függvény ugyanarra a sémára épül mint a decode_tires, csak annyi a különbség, hogy 4 bitenként kapjuk az adatokat, nem pedig egyesével, mivel itt minden panelnek 4 adatát tudjuk majd még lekérdezni. Nem tudom lehet-e még ehhez a részhez hozzáfûzni valamit, szóval szerintem ugorjunk. 2.5 encode_doorsHáttér információ:A SA:MP képes minden jármû 4 \"ajtajának\" állapotát tárolni. Ezek az \"ajtók\"(Doors-t hogy fordíthatnám jobban?) konkrétan a motorháztetõ, csomagtartó, vezetõoldali ajtó, és anyósülés oldali ajtó. A 2 hátsó ajtó állapotát jelenleg még nincs lehetõségünk lekérdezni.Amikor lekérdezzük egy jármû \"ajtajának\" adatait, azt a SA:MP 4 bájtként(32 bit) adja vissza. Ebbõl a 4 bájtból 1 bájt(8 bit) jut minden egyes objektumra. Vagyis minden egyes objektum adatait 8 bit tárolja. Vagyis tárolhatná...A SA:MP csak az elsõ 3 bitet használja, a további bitek üresek lesznek, nincs funkciójuk, talán majd késõbb lesz. Az elsõ bit azt tárolja, hogy az objektum zárt[0], vagy nyitott[1](nem az jelenti, hogy bezárt, hanem becsukott/kinyitott) állapotban van-e. A második bit azt adja meg, hogy az objektum ép[0] vagy sérült[1]. A 3. bit arról tájékoztat, hogy az adott objektum rajta van-e még a jármûvön[0] vagy már leesett[1]. A többi bit mint mondtam üres. Ez majd a decode függvénynél lesz fontos.Nézzük tehát a konkrét kódot: encode_doors(bonnet, boot, driver_door, passenger_door, behind_driver_door, behind_passenger_door){ #pragma unused behind_driver_door #pragma unused behind_passenger_door return bonnet | boot << 8 | driver_door << 16 | passenger_door << 24;} A 2 hátsó ajtóval nem foglalkozunk, mivel azok jelenleg még nem támogatottak ugyebár, de a kód készítõje gondolt a jövõre is, vagy fogalmam sincs miért adta meg ezt a két paramétert is, hisz ígyis-úgyis módosítani kell a kódon... Ha behoznak még 2 ajtót, akkor az 1 bájtos tárolással már 6 bájt lenne, de 1 változó maximum 4 bájt lehet, tehát a kódot úgyis módosítani kell, ha meg arra gondoltak, hogy legalább a paramétereket nem kell majd szegény scriptereknek minden sorban átírni, az megint hülyeség, hisz ki adna értéket olyan paraméternek, amely nincs használva?...Nos mindegy, nem a kritizálásért vagyunk most itt. Ha megnézzük, a kód 8-as shiftelést használ, mivel egy objektum adatait 8 bit tárolja. A mûködési elv ugyanaz, mint az elõzõ encode függvényeknél, így erre nem kívánok megint kitérni.Nézzük azonban hogyan írnánk ehhez egy decode függvényt. Mikor megkérdeztem errõl ismerõsömet, õ azt mondta, ugyanúgy mint az elõzõ, decode_panels függvénynél, csak 8-as shiftelést használunk, és 15 helyett 255-el végezzük a bitenkénti ÉS mûveletet. 255-el? minek?No erre is ki fogok térni késõbb, de haladjunk a szokásos módon. 1 bemeneti paraméterünk van, és 4 kimeneti, vagyis a header rész a következõ: decode_doors(doors, &bonnet, &boot, &driver_door, &passenger_door) Ezzel meg is vagyunk, itt nem látok különösebb magyarázni valót. Én most nem fogok törõdni a 2 nem létezõ ajtóval, ha egy jövõbeni verzióban netán azokat is behoznák mint lekérdezhetõ objektum, akkor ígyis-úgyis módosítani kell majd a kódot, addig meg úgysincs rá szükségünk.Nos, mint az eddigi decode függvényeknél, elsõ feladatunk egy shiftelés mentes bitenkénti ÉS mûvelet elvégzése. Az egyik megoldás a következõ: bonnet = doors & 255; Lehet így is. Azonban véleményem szerint felesleges itt egy ekkora számmal \"szenvednünk\". Miért használjunk ekkora számot, és ezáltal bonyolítsuk feleslegesen a kódunkat, ha nekünk a 8 bitbõl csak az elsõ 3 lesz ami adatot tárol? Bõven elegendõ egy olyan szám, amely bináris alakja 111. Ez nem más, mint a 7. Tehát a következõ megoldás ugyanúgy helyes: bonnet = doors & 7; A két kód pontosan ugyanazt az eredményt fogja visszaadni, semmi különbség a kettõ között. Csupán elsõ esetében nagy számmal dolgozunk, még második verzió esetén csak akkorával, amekkora szükséges.A kód többi részében csak 8-as shifteléssel végigcammogunk a doors változó többi bitjén is: boot = doors >> 8 & 7;driver_door = doors >> 16 & 7;passenger_door = doors >> 24 & 7; A teljes kód egyben: decode_doors(doors, &bonnet, &boot, &driver_door, &passenger_door){ bonnet = doors & 7; boot = doors >> 8 & 7; driver_door = doors >> 16 & 7; passenger_door = doors >> 24 & 7;} A kód pont ugyanazon az elven épül fel, mint az összes többi decode függvényünk, csak épp a benne használatos értékek az \"ajtó\" objectumok adatstruktúrájához vannak igazítva.2.6 Egyéb ötletek gyakorlati alkalmazásra, hasznosításraJoggal felmerülhet a kérdés, hogy ennyi? Emiatt rágtuk végig magunkat ezen néhány tízezer karakteren? Nem. Nem ennyi. A SA:MP-ban rengeteg felhasználási módja lehet még a bitmûveleteknek. Mutatok is néhány dolgot, mire jók még.2.6.1 RGBA->HEX; HEX->RGBA konvertálásokNos, bármily \"hihetetlen\", erre is használható. Miért is? Nos, 16-os számrendszerben 0-9, valamint A-F \"szánjegyek\" léteznek. A hexadecimális számjegy decimális értékben 0-15 lehet. Ha megnézzük bináris értékeket, 0-15 az 4 bittel leírható, hiszen a 15 az bináris alakban 1111. De ha józan paraszti ésszel belegondolunk is. 16 az 24. Így tehát 1 hexadecimális érték, az 4 bináris értékkel írható le. Ebbõl már sejthetõ, hogy itt is shiftelésre lesz szükségünk. Naná Mivel 4 bit ír le egy hexadecimális számjegyet, így 4-esével kell majd shiftelnünk.Mi is az az RGBA?RGBA: A színek olyan meghatározási módja, melyben a 3 alapszín([r]ed, [g]reen, lue) mellett egy 4. érték, az úgynevezett [a]lpha is szerepel, amely az áttetszõséget adja meg. A legtöbb program(pl. paint, PS), és pl. a css(külön függvénnyel!) is elfogadja ezen értékeket decimális számokként(0-255) is, a SA:MP-ban azonban hexadecimális alakban kell megadnunk. Bármily furcsa a következõ 4 sor pont ugyan azt jelenti:\"Normál\" RGB alak(pl. paint, photoshop): Piros: 0, Zöld: 255, Kék: 255CSS alak(függvénnyel): rgba(0, 255, 255, 1);Pawn alak(1): 0x00FFFFFFPawn alak(2): 16777215 Ó igen, a Pawn-ban decimális számokként is megadhatjuk a színeket, hisz a program mindent bináris számrendszerbe fog átszámolni. De akár bináris alakban is megadhattuk volna:Pawn alak(3): 0b111111111111111111111111 Az, hogy mi a Pawn-nak hexadecimális alakban szoktuk megadni, annak az egyetlen oka, hogy így a leginkább \"átlátható\", és ez egy amúgy is elfogadott módja (mármint a hexadecimális számmal való megadás) a színek leírásának a számítástechnikában.Nos, mivel 1 szín 0 és 255 közötti értéket tartalmaz a piros, zöld és kék színekbõl, ez hexadecimálisan 2 számjeggyel leírható: 00-FF. Az FF értéke 255, aki nem hiszi, számoljon utána (162-1)2 hexadecimális számjegy az pedig 8 bináris számjeggyel leírható(00000000-11111111). Így már láthatjuk, hogy 8-as shiftelésre lesz szükségünk. Másra nincs is szükségünk, az RGBA->HEX konverter elkészítéséhez.stock RGBAToHex( r, g, b, a ){return a | b << 8 | g << 16 | r << 24;} Az, hogy miért fordított sorrendben kellett megadnunk az értékeket, az megint csak azzal magyarázható, hogy a biteket hétköznapi értelemben véve visszafelé számozzuk.Egy ellentét, azaz HexToRGBA függvény megírása sem túl bonyolult, ugyan ezt kell csinálnunk, csak visszafelé.stock HexToRGBA( colour, &r, &g, &b, &a ){a = colour & 0xFF;b = (colour >> 8 ) & 0xFF;g = (colour >> 16 ) & 0xFF;r = (colour >> 24 ) & 0xFF;} A 0xFF-et természetesen decimális értékkel is megadhatjuk(255).No akkor lehet ugye RGBA-t ARGB-be is konvertálni, az így néz ki:RGBAToARGBHEX(r,g,b,a){return (r & 0xff) << 24 | (g & 0xff) << 16 | (b & 0xff) << 8 | a & 0xff;} Ez máris egy újabb felhasználási módja a bitmûveleteknek. De, mutatok még 2.6.2 Boolen változók helyett IntegerAz al-al-alcímet elolvasva joggal felmerülhet a kérdés, hogy ez meg hogy jön ide. Nos ez úgy jön ide, hogy most egy kis memóriatakarékosságról fogok itt regélni. Adhattam volna azt is címnek, hogy miért ne használjunk boolean változókat, ha legalább 5 igaz/hamis értéket el akarunk tárolni.Nos akkor egy kis memórialecke. Azt már tisztáztuk, hogy az integer változók 32 bitesek, vagyis 4 bájtot foglalnak le a memóriában maguknak. A boolean típusú változókról azonban még nem volt szó. Mivel 1 értéket tárolnak csak, az is csak igaz vagy hamis lehet, így joggal gondolhatnánk, hogy 1 bit a mérete. Azonban nem...1 boolean változó 8 bitet, azaz 1 bájtot foglal le magának a memóriában. Hogy ez miért így van, az a számítógépek memóriahasználatával magyarázható. Manapság nincs mód arra, hogy a memóriában egyetlen bitet módosítsunk, csak 8-as \"csomagokban\", vagyis bájtonként tudjuk ezt megtenni. Hogy ez miért van így, azt most nem fogom itt fejtegetni Nos, ha megnézzük tehát, a boolean változó egy nagyon-nagyon pazarló megoldás. Hiszen 1 darab boolean változó 1 bájtot foglal, 4 darab boolean változó esetén ez már 4 bájt, ami megegyezik 1 integer változó méretével. Azonban, még a 4 boolean változóban 4 igaz/hamis érték kerülhet tárolásra, addig egy integerben 32 igaz/hamis értéket is eltárolhatunk.Bizony, 32 igaz/hamis érték. Hiszen 1 integer változó 32 bitbõl áll, ha minden bitet hasznosítunk, akkor a bitmûveletek felhasználásával 32 igaz/hamis értéket is eltárolhatunk. Összehasonlítva:32 darab boolean változó mérete 32 bájt.1 integer változó mérete: 4 bájt. Vagyis 32 boolean változó esetén nyolcad annyi memóriánkba kerül, ha egy darab integerben tároljuk el az értékeket. Persze 32 booleanra ritkán van szükség, de egy RP modban simán elõfordulhat. Gondoljunk csak bele... Van 32 boolean-tömbös változónk, ez a maximum slotszámot(1000) figyelembe véve: 32*1000 byte azaz 32000 byte ami ~31 kilobyte. Ha ezt egyetlen integer-tömbbel oldanánk meg: 4*1000 byte, azaz 4000 byte, ami ~3,9 kilobyte. Azért így nézve már nem annyira \"elhanyagolandó\" az a különbség szerintem =)Akár írhatnánk erre külön egy függvényt, mely az adott bit értékével tér vissza. Nem is bonyolult. Így már azok számára is használhatóak a bitmûveletek, akik nem értenek hozzá, és rengeteg memóriát takaríthatnak meg.Íme egy függvény az adatok kiolvasására:stock GetData(integer, bit){ return integer >> bit & 1; //vagy az 1 helyett akár 0x01 alakban is megadhatjuk, nincs jelentõsége} És a párja, adat írásra:stock SetData(&integer, bit, value){ integer |= value << bit; return 1;} Persze ezt még lehetne csinosítani, hogy pl. ne 0-tól hanem 1-tõl számoljuk a biteket, vagy hogy 31(vagy 32 ha 1-tõl számolunk) felett 0 legyen a return értéke, esetleg a value-re is rakhatnánk ellenõrzést, hogy 0-1-e az értéke, vagy más, de ilyenekkel most nem törõdtem.2.6.3 Több érték, egy integerbeNa ez az a rész, ami véleményem szerint már csak a nagyon megrögzött memóriafelszabadítók szerint nem lesz felesleges Amennyiben nincs szükségünk túl nagy számokra(pl. csak 0-15, vagy akár csak 0-7, de akár még a 0-255 is megfelel), akkor akár egy integerbe is tehetjük õket. Hogyan? Egyszerû. Csoportosítjuk a biteket. Tegyük fel, hogy maradván egy RP szervernél, el akarjuk tárolni ûbergigantikusan memóriakímélõ megoldással a játékos házának számát(ez legyen mondjuk 0-100 közötti szám), a játékos frakcióját(ez legyen 0-15 között), a játékos rangját(0-7 között), hogy a telefonja be van-e kapcsolva(0-1), a játékos szolgálati jármûvének id-jét(legyen 0-255 között), és az alapmunkáját(0-15 között). Ez is megoldható, egyetlen integerben, és ismételten 1000 slottal számolva máris megspóroltunk 4*4*1000+1*1000 byte-t(5 integer, és 1 boolean helyett csupán 1 integert használunk), ami ~16,6 kilobyte. Azért nem mindegy az, hogy 20,5 kb-ot vagy 4 kb-ot használunk szerintem... Ezt a következõ módszerrel valósíthatnánk meg:#define DATA_HOUSE 0#define DATA_FACTION 1#define DATA_RANK 2#define DATA_PHONESTATE 3#define DATA_DUTYCAR 4#define DATA_JOB 5stock GetData(playerinfo, data){ switch(data) { case DATA_HOUSE: return playerinfo & 127;//7 bit case DATA_FACTION: return playerinfo >> 7 & 15;//4 bit case DATA_RANK: return playerinfo >> 11 & 7;//3 bit case DATA_PHONESTATE: return playerinfo >> 14 & 1;//1 bit case DATA_DUTYCAR: return playerinfo >> 15 & 255;//8 bit case DATA_JOB: return playerinfo >> 23 & 15;//4 bit}return -1;}stock SetData(&playerinfo, data, value){ switch(data) { case DATA_HOUSE: { playerinfo = playerinfo & ~127 | value; }//7 bit (0-127 közötti maximálisan) case DATA_FACTION: { playerinfo = playerinfo & ~(15 << 7) | value << 7; }//4 bit (0-15 között) case DATA_RANK: { playerinfo = playerinfo & ~(7 << 11) | value << 11; }//3 bit (0-7 között) case DATA_PHONESTATE: { playerinfo = playerinfo & ~(1 << 14) | value << 14; }//1 bit (0-1) case DATA_DUTYCAR: { playerinfo = playerinfo & ~(255 << 15) | value << 15; }//8 bit (0-255 között) case DATA_JOB: { playerinfo = playerinfo & ~(15 << 23) | value << 23; }//4 bit (0-15 között) } return -1;}stock SetDatas(&playerinfo, house, faction, rank, phonestate, dutycar, job){playerinfo |= house | (faction << 7) | (rank << 11) | (phonestate << 14) | (dutycar << 15) | (job << 23);} Ez már azért elég morbid, ezt inkább csak érdekességképp mutattam, de éppenséggel ez is használható lenne, csak ehhez már kell egy kis õrültség 2.7 Második fejezet végeNos, láthattuk hogyan is mûködik mindazon sok \"marhaság\", amit az elsõ fejezetben elméletként átvettünk. Láthattuk hogyan mûködnek a jármûvek paneljének adataival babráló encode függvények, valamint megmutattam, hogyan írhatunk hozzájuk decode verziókat. Láthattunk még példát egyéb gyakorlati hasznosításra, kicsit megkoccoltuk a hexadecimális számok világát is, majd szóba került egy kis memóriatakarékosság. Végül mutattam egy összetettebb, elvetemült példát is, a bitmûveletek hasznosítására.3. Összegzés, végszó Úgy gondolom, hogy elég részletesen körbe jártuk a témát. Megnéztük a bitmûveletek elméleti, és gyakorlati oldalát is, láthattunk gyakorlati példákat. Nem kell elsõre megérteni ezt az egészet, sõt... Elegendõ ha egyszer elolvassuk, emésztjük kicsit, majd késõbb megint elõvesszük, akkor netán már kicsit többet is megértünk belõle. Nem egy kihagyhatatlan része a Pawno-nak, de azért van annak haszna, ha az ember jártas ebben a témában is, bár sokak szerint nem éri meg a sok fáradtságot, és idõt, mely a megértéséhez, begyakorlásához, használatának elsajátításához szükséges. Próbáltam mindent részletesen kifejteni. A bonyolultabb dolgokhoz sokat magyarázni, az egyszerûbbek esetén csak visszautalni az alapokhoz. Sok dolgot talán már túl is magyaráztam. Ezt a leírást nem 1 nap alatt készítettem el, kb. 1 hónapja írtam meg az elméleti oldalát, most az elmúlt pár napban pedig a gyakorlatot. Esténként 1-2 órát szántam rá csupán, és mivel ilyenkor már nem vagyok épp a toppon, elképzelhetõ, hogy néhány helyen helyesírási, vagy elvi hibát vétettem. Amennyiben valaki ilyet találna, kérem szóljon, és korrigálni fogom. Nos, azt hiszem nincs is más hátra, mint hogy sok szerencsét kívánjak a bitmûveletek elsajátításához, és megosszam a forrásokat. Remélem néhányan hasznosnak találják, eme igen hosszúra sikerült leírást.4. Források: Internet:WikipédiaSA:MP WikiSA:MP hivatalos fórumKönyv, e-book:Pohl László - A programozás alapjai(BME - Elektronikus Eszközök Tanszéke, Budapest, 2010)Siroki László - C programozás - Bitmûveletek jegyzeteˆ Anthony, 2012 - Minden jog fenntartva.A leírás egészben, vagy részleteiben szabadon terjeszthetõ, ameddig egyértelmûen fel van tüntetve az eredeti készítõ, valamint hogy honnan is származik. « Utoljára szerkesztve: 2012. augusztus 01. - 10:53:54 írta Anthony » Naplózva
11111111111111111111111111110000//-8>>211111111111111111111111111111100//-2[/quote]Láthatjuk, hogy itt fontos szerepe van az MSB-nek. Ettõl függ ugyanis, hogy 0-k vagy 1-esek kerülnek a \"megüresedett\" bitek helyére. Fontos tehát megjegyezni, hogy a jobbra shiftelés megtartja a számok elõjelét, nem változtat azon!1.4.5.2 Balra toló operátor(<<):Ez az operátor a szám bitjeit balra tolja a megadott mértékkel. Egy bittel való eltolás, 2-vel való szorzásnak felel meg. Nézzünk egy példát: 00000000000000000000000000001000//8<<200000000000000000000000000100000//32[/quote]Itt megint könnyedén észrevehetõ mi is történt. Minden bitet fentebb \"toltunk\" 2-vel. Azok a bitek, amelyek ezáltal a 32. bit elé kerültek, kiesnek. Az így megüresedett bitek 0-kkal egészültek ki. Eddig nem túl bonyolult. Azonban figyeljünk oda, ha túl sokáig shiftelünk egy pozitív számot, akkor az utolsó érték, melyet fel tud venni a változónk (mielõtt még túlcsordulás miatt elérné a 0-t), az egy negatív szám!Nézzük mi történik negatív számok esetén: 11111111111111111111111111111000//-8<<211111111111111111111111111100000//-32[/quote]Láthatjuk hogy a balra shifteléskor a szám mindig 0-kkal egészítõdik ki, legyen az akár negatív, akár pozitív. Nem veszi figyelembe az MSB értékét, így ha túl sokat shiftelünk, akkor elõfordulhat, hogy a szám elveszti negatív értékét, és eléri a 0-t.1.4.6 Logikai biteltolás (logical shift, logikai shiftelés) operátorElõször is le kell tisztázni, hogy logikai shiftelésbõl csak egy létezik, jobbra logikai shiftelés(>>>). Sokban hasonlít a jobbra shiftelõ operátorra (>>), azonban van egy fontos különbség.Nézzük az elsõ példát: 00000000000000000000000000001000//8>>>200000000000000000000000000000010//2[/quote]Eddig nem igazán tapasztalhattunk semmi változást, minden pont ugyanúgy zajlott, mint a jobbra shiftelésnél(>>).Nézzük mi történik negatív szám esetén: 11111111111111111111111111111000//-8>>>200111111111111111111111111111110 //1073741822[/quote]Itt már láthatjuk a különbséget. Logikai shiftelés esetén az üres bitek helyére bekerülõ érték nem függ az MSB-tõl, hanem mindig 0. Emiatt soha nem kaphatunk eredményül negatív számot, logikai shiftelés esetén.1.5 Elsõ fejezet végeNos, ezennel átvettünk mindent, amit érdemes tudni a bitmûveletekrõl. Megmutattam az alapok, hogy hogyan épülnek fel a változók, valamint átvettük a fõ operátorokat. Eddig csak színtiszta elmélet volt. A következõ részben megmutatom, hogyan hasznosíthatjuk mindezt a gyakorlatban, vagyis mire is jó az a sok marhaság, amit eddig leírtam.2. Gyakorlati példák, érdekességek 2.1 Áttekintés, mikrõl is lesz még szóAz alábbiakban még pár dolgot szeretnék mutatni. Elõször is néhány \"függvénnyel\" fogok foglalkozni, majd mutatok még egy-két érdekességet. Konkrétan megmutatom, hogyan tudjuk egy jármû minden \"paneljének\" adatát kiíratni, részletesen(erre alapból nincs lehetõség, és a függvények is mind \"encode\" függvények, én viszont mutatok egy-egy decode \"verziót\" is.)Az alábbi 4 függvénnyel fogok foglalkozni: encode_tiresencode_panelsencode_doorsencode_lightsDe elõbb egy kis háttérinfó:A Pawnban a forrásban megadhatunk bináris számokat ugyanúgy, ahogyan Hex számokat is megadhatunk. Ehhez a \"0b\" prefixet kell használnunk. Tehát ha bináris számunk az 1011 és ezt akarjuk adni értékül egy változónak, azt így tehetjük meg: new var = 0b1011; 2.2 encode_tiresHáttér információ:A SA:MP-ban egy jármû abroncsainak adataid 4 bit tárolja. Az elsõ bit a jobb hátsó, a második bit a jobb elsõ, a harmadik bit a bal hátsó, a negyedik bit pedig a bal elsõ abroncsról tárol információt. Mivel 4 bitben eltárolhatóak az információk, így decimális formában 0-15 értékekkel is be lehet állítani a jármûvek abroncsait. 16-tól kezdve az értékek már ismétlõdni kezdenek, szóval a 16-nál az eredmény ugyanaz mint 0-nál, 17-nél ugyanaz mind 1-nél, és így tovább. Ez érthetõ, ha tudjuk hogyan épül fel a kettes számrendszer.(Ne felejtsük el, a biteket hátulról számozzuk!)Nos nézzük hogyan néz ki a kód: encode_tires(tire1, tire2, tire3, tire4){ return tire1 | tire2 << 1 | tire3 << 2 | tire4 << 3;} Láthatjuk, hogy bekérjük a 4 abroncs adatát(vagyis hogy ép[0], vagy kidurrant[1]), majd egy sima bitenkénti VAGY mûvelet és biteltolás kombinálásával visszatérünk az eredménnyel. A kódban a tire1 felel meg a jobb hátsó abroncsnak, a tire2 a jobb elsõ, a tire3 a bal hátsó, és a tire4 a bal elsõnek. Nézzünk egy konkrét példát. Tegyük fel, hogy én a hátsó 2 abroncsot ki akarom durrantani, még az elsõ kettõt épre akarom beállítani. Ez esetben így néz ki a dolog: encode_tire(1,0,1,0); Ha kiíratnánk konzolba a visszatérési értéket (%d-vel), 5-öt kapnánk. Miért is? Az 5 bináris alakja 0101. Hoppá, ha megnézzük ez pont fordítottja annak, amit mi megadtunk számjegyekként, csak már konkrét bináris számként. Nézzük hogyan is történhetett mindez, mit is csináltunk. A függvény a következõ mûveletet végzi: tire1 | tire2 << 1 | tire3 << 2 | tire4 << 3 Konyhanyelven úgy magyarázhatnánk el a dolgot, hogy ez a kód egymás mellé pakolgatja a megadott számokat, fordított sorrendben. Na de mi most nem a konyhanyelv miatt vagyunk ebben a leírásban, szóval nézzük a kódunkat. Lényegében ez futott le, a konkrét értékeinkkel nézve: 1 | 0 << 1 | 1 << 2 | 0 << 3 Bármily furcsa is, ez egy mûvelet, melyen szépen végig halad sorban a program. Elõször is írjuk át az egészet bináris alakba(az egyszerûség kedvéért most csak 4 bitet fogok kiírni): 0001 | 0000 << 1 | 0001 << 2 | 0000 << 3 Na erre se mondtuk volna rá a tutorial elsõ részének elolvasása elõtt, hogy ez egy mûveletsor. A rendszer elsõnek az eltolásokat végzi el (olyan ez mint a 2*3+2*4+2*5 mûveletben elõször a szorzásokat végezzük el, és csak utána adunk össze, ha nincs zárójel), és csak utána a bitenkénti VAGY mûveleteket, ezért mi is így fogunk tenni. Emlékezzünk, eltoláskor az \"üres\" bitek 0 értéket fognak felvenni. Végezzük el külön csak az eltolásokat: 0000 << 1 = 00000001 << 2 = 01000000 << 3 0000 Nos, ha ezeket elvégeztük, akkor nézzük, hogy is áll most a kódunk: 0001 | 0000 | 0100 | 0000 Négy azonos mûvelet van hátra, így hát a program innen más szépen sorban fog haladni. Tegyünk mi is így. Elsõ tehát a 0001 | 0000 mûvelet elvégzése. Emlékeztetõül, a bitenkénti VAGY mûvelet akkor ad 0-t eredményül, ha mind a kettõ érték 0, minden más esetben 1-et. Végezzük el külön az elsõ bitenkénti VAGY mûveletet: 0001 | 0000 = 0001 Nem volt bonyolult, nézzük tehát hogy áll akkor most a teljes mûveletünk: 0001 | 0100 | 0000 Haladunk szépen tovább, elvégezzük a 0001 | 0100 mûveletet. 0001 | 0100 = 0101 Nincs mit magyarázni rajta, egyszerû bitenkénti VAGY mûvelet volt ez is. Már csak ez van hátra: 0101 | 0000 Nos, ez sem lesz egy bonyolult mûvelet: 0101 | 0000 = 0101 Ezzel meg is kaptuk a végeredményt, ami nem más, mint a 0101 (tízes számrendszerben 5). Mivel azonban eléggé kusza lett így a számításunk, nézzük egyben hogyan is néz ki az egész, ha nem bontom ennyire részekre: 0100 | 0000 << 1 | 0001 << 2 | 0000 << 3v0001 | 0000 | 0100 | 0000v0001 | 0100 | 0000v0101 | 0000v0101 Nos, ezennel láthattuk a gyakorlatban is, hogyan mûködik a függvény. Kijelenthetjük tehát, hogy a függvény a számokat a sorrendjüknek megfelelõ számú bitbe helyezi. Mivel azonban a biteket hátulról számozzuk, így az 1,0,1,0 bemenetek végeredménye 0101 lesz. Így már nem is olyan bonyolult Na, ha már ilyen sokáig eljutottunk, akkor felhasználhatjuk ezt a kódot arra, hogy az alapjait figyelembe véve elkészítsük a decode változatot. Mivel 4 értékkel térünk vissza, ezért paraméterként kell majd 4 változó, melyeknek módosíthatjuk az értékét, illetve 1 paraméter pedig a dekódolandó szám. A függvényünk \"címsora\" tehát így néz ki: decode_tires(tires, &tire1, &tire2, &tire3, &tire4) Hogy példánk ne csak elmélet legyen, a tires változó legyen az elõbbi 0101 értékkel egyenlõ. Na akkor menjünk tovább. Bemeneti értékünk ugye a \"tires\" változóban lesz eltárolva, így ezzel fogjuk a mûveleteket végezni. Kezdjünk el gondolkozni. Hogyan is tudnánk ellenõrizni egy adatot. Legegyszerûbb ha a bitenkénti ÉS mûveletet alkalmazzuk. Miért is? Nos, a bitenkénti ÉS mûvelet ugye akkor ad eredményül 1-et, ha mind a 2 érték egy. Tehát ha min egy számot összehasonlítunk a 1-el(binárisan ugye 00000000000000000000000000000001, akkor az eredményünk lényegében arról fog tájékoztatni, hogy az elsõ bit 0, vagy 1. Nem is olyan bonyolult ez. Nézzük akkor az elsõ bitet. Arra emlékezhetünk még, hogy az elsõ bit a tire1, vagyis jobb hátsó abroncs adatait tárolja. Tehát: tire1 = tires & 1; Vagy konkrétan a példánk esetében: tire1 = 0101 & 0001 Mivel a bitenkénti ÉS mûvelet akkor ad 1-et eredményül, ha mind a kettõ bit 1, akkor már láthatjuk is, hogy miért elegendõ az elsõ bitet szemügyre venni. Mivel mind a 2 helyen az elsõ bit 1, ezért a tire1 változónk értéke is 1(0001) lesz. Egyszerû és nagyszerû, máris megkaptuk a jobb hátsó abroncs állapotát.Gondolkozzunk hát tovább. Nekünk most arra van szükségünk, hogy a 2. bit értékét megkapjuk. Hogyan kaphatjuk ezt meg? Legegyszerûbb megoldás, ha a tires változót jobbra shifteljük 1-el, majd ismételten bitenkénti ÉS mûveletet végzünk. Nézzük tehát, hogyan is nézne ez ki: tire2= tires >> 1 & 1; Vagy konkrétan a mi példánkban: tire2 = 0101 >> 1 & 0001vtire2 = 0010 & 0001 Ismételten bitenkénti ÉS mûvelet, és az eredményünk 0, mivel csak az egyik bit 1, a másik 0, így a bitenkénti ÉS mûvelet eredménye is 0. Ezen az elven megcsináljuk a maradék 2 abronccsal is: tire3= tires >> 2 & 1;tire4= tires >> 3 & 1; Vagy a mi esetünkben konkrét példánkkal: tire3 = 0101 >> 2 & 0001 // azaz: --> 0001 & 0001 = 1tire4 = 0101 >> 3 & 0001 // azaz: --> 0000 & 0001 = 0 A teljes kódunk tehát: decode_tires(tires, &tire1, &tire2, &tire3, &tire4){ tire1 = tires & 1; tire2 = tires >> 1 & 1; tire3 = tires >> 2 & 1; tire4 = tires >> 3 & 1;} A konkrét példánkban a 4 változónk értéke a függvény lefutása után: tire1: 1tire2: 0tire3: 1tire4: 0 Ha összehasonlítjuk, ezek pont azok az értékek, mint amiket az encode függvényben megadtunk, tehát úgy tûnik jól dolgoztunk 2.3 encode_lightsEz a rész lesz a legrövidebb. Az egész lámpás rész pontosan ugyanarra a sémára épül mint az abroncsok. Az adott bitek ugyanazon a \"helyen\" lévõ lámpa adatait szolgáltatják, ugyanúgy 0 az ép, és 1 a sérült lámpák állapot azonosítója, és ugyanúgy 1-1 bit tárolja az értékeket.Maga a kód: encode_lights(light1, light2, light3, light4){ return light1 | light2 << 1 | light3 << 2 | light4 << 3;} Láthatjuk hogy halál pontosan ugyanaz mint az encode_tires, csak más változónevekkel. Így hát a decode függvény is ugyan az lesz, csak a változóneveket kell módosítanunk: decode_lights(lights, &light1, &light2, &light3, &light4){ light1 = lights & 1; light2 = lights >> 1 & 1; light3 = lights >> 2 & 1; light4 = lights >> 3 & 1;} Ha megnézzük látjuk, hogy teljesen ugyan az a séma, ugyan az a felépítés, minden megegyezik, ezért nem is írtam le újból ugyanazt, amit egyszer már kifejtettem.2.4 encode_panelsHáttér információ:A SA:MP 7 panelrõl tárol adatokat. Egy panel adatait 4 bit írja le. Nos, ennél több infóra nekünk most nincs szükségünk, az elõzõ kód alapján ezen kód is megérthetõ, így az encode verzióhoz nem is nagyon szeretnék magyarázatot fûzni. A különbség az encode_tires-hez képest, hogy itt 4-essével shiftelünk, mivel 1 panel adatait 4 bit tárolja.Maga a kód: encode_panels(flp, frp, rlp, rrp, windshield, front_bumper, rear_bumper){ return flp | frp << 4 | rlp << 8 | rrp << 12 | windshield << 16 | front_bumper << 20 | rear_bumper << 24;} Nézzünk egy decode verziót, ehhez a kódhoz. Alapjaiban ugyanazon a vonalon kell elindulnunk, mint a decode_tires esetében. Itt most 7 visszatérési értékünk lesz, és 1 bemeneti érték. Tehát így néz ki a függvény header: decode_panels(panels, &flp, &frp, &rlp, &rrp, &windshield, &front_bumper, &rear_bumper) A folytatásban megint csak az elõbbi verziót kell alapul vennünk, azonban itt lesz egy kis különbség. Egyértelmû, hogy mivel 1 panel adatait 4 bit tárolja, így a 4 bitet nekünk egyben kell lekérni, és egyben kell vele visszatérnünk. Megint csak a bitenkénti ÉS mûveletet fogjuk alkalmazni, azonban mivel 4 bitrõl van szó, így egy olyan számmal kell összehasonlítanunk, mely bináris alakban 1111. Ez a szám nem más, mint a 15. Hogy miért ez kell?Ha egy bitsorozat tartalmát ki szeretnénk másolni egy másik változóba, akkor a bitenkénti ÉS mûveletre vna szükségünk. A bitenkénti ÉS mûvelet akkor ad 1-et eredményül, ha mindkét bit 1, ha valamelyik 0, akkor a visszatérés már 0 lesz. Ergó tehát, ha egy csupa 1-esekbõl álló bitsorozattal vetjük össze az eredeti bitsorozatunkat, akkor lényegében lemásoljuk a sorozatot, egy másik változóba, hisz eredményül mindig az eredeti sozatot fogjuk visszakapni. Íme néhány példa: 0101 & 1111 = 01011111 & 1111 = 11110000 & 1111 = 00001011 & 1111 = 1011 Láthatjuk tehát, hogy ha csupa 1-est tartalmazó bitsorozattal hasonlítjuk össze az eredeti számunkat, akkor lényegében egy másolatot készítünk. Mivel nekünk erre a 4 bitre van szükségünk a 32-bõl, így pontosan ezt kell tennünk, ezen 4 bitet lemásolni egy másik változóba, hogy késõbb ezt majd egy másik függvénnyel még jobban darabokra szedhessük.Íme tehát akkor a kódunk elsõ sora: flp = panels & 15; Mivel 4-esével vizsgáljuk a biteket, így lényegében annyit kell még változtatnunk az elõzõ kódon(decode_tires), hogy az 1-esével növekvõ shiftelések helyett 4-esével shifteljünk. Azaz a 2. panel adatainak lekérdezése: frp = panels >> 4 & 15; Innen pedig már csak mindig további 4 shiftelést el kell végeznünk, és szépen végig haladhatunk a panels változó bitjein, és megkaphatjuk minden panel adatait külön-külön. rlp = panels >> 8 & 15;rrp = panels >> 12 & 15;windshield = panels >> 16 & 15;front_bumper= panels >> 20 & 15;rear_bumper = panels >> 24 & 15; Ezennel minden panel adatait megkaptuk. Ezután már akár a decode_tires függvényt felhasználva szét lehet szedni az adatokat darabokra, de akár egy saját névvel, és más változó nevekkel ellátott másik függvényt is létre lehet hozni a decode_tires mintájára.Tehát az egész kód egyben: decode_panels(panels, &flp, &frp, &rlp, &rrp, &windshield, &front_bumper, &rear_bumper){ flp = panels & 15; frp = panels >> 4 & 15; rlp = panels >> 8 & 15; rrp = panels >> 12 & 15; windshield = panels >> 16 & 15; front_bumper= panels >> 20 & 15; rear_bumper = panels >> 24 & 15;} Igazából a függvény ugyanarra a sémára épül mint a decode_tires, csak annyi a különbség, hogy 4 bitenként kapjuk az adatokat, nem pedig egyesével, mivel itt minden panelnek 4 adatát tudjuk majd még lekérdezni. Nem tudom lehet-e még ehhez a részhez hozzáfûzni valamit, szóval szerintem ugorjunk. 2.5 encode_doorsHáttér információ:A SA:MP képes minden jármû 4 \"ajtajának\" állapotát tárolni. Ezek az \"ajtók\"(Doors-t hogy fordíthatnám jobban?) konkrétan a motorháztetõ, csomagtartó, vezetõoldali ajtó, és anyósülés oldali ajtó. A 2 hátsó ajtó állapotát jelenleg még nincs lehetõségünk lekérdezni.Amikor lekérdezzük egy jármû \"ajtajának\" adatait, azt a SA:MP 4 bájtként(32 bit) adja vissza. Ebbõl a 4 bájtból 1 bájt(8 bit) jut minden egyes objektumra. Vagyis minden egyes objektum adatait 8 bit tárolja. Vagyis tárolhatná...A SA:MP csak az elsõ 3 bitet használja, a további bitek üresek lesznek, nincs funkciójuk, talán majd késõbb lesz. Az elsõ bit azt tárolja, hogy az objektum zárt[0], vagy nyitott[1](nem az jelenti, hogy bezárt, hanem becsukott/kinyitott) állapotban van-e. A második bit azt adja meg, hogy az objektum ép[0] vagy sérült[1]. A 3. bit arról tájékoztat, hogy az adott objektum rajta van-e még a jármûvön[0] vagy már leesett[1]. A többi bit mint mondtam üres. Ez majd a decode függvénynél lesz fontos.Nézzük tehát a konkrét kódot: encode_doors(bonnet, boot, driver_door, passenger_door, behind_driver_door, behind_passenger_door){ #pragma unused behind_driver_door #pragma unused behind_passenger_door return bonnet | boot << 8 | driver_door << 16 | passenger_door << 24;} A 2 hátsó ajtóval nem foglalkozunk, mivel azok jelenleg még nem támogatottak ugyebár, de a kód készítõje gondolt a jövõre is, vagy fogalmam sincs miért adta meg ezt a két paramétert is, hisz ígyis-úgyis módosítani kell a kódon... Ha behoznak még 2 ajtót, akkor az 1 bájtos tárolással már 6 bájt lenne, de 1 változó maximum 4 bájt lehet, tehát a kódot úgyis módosítani kell, ha meg arra gondoltak, hogy legalább a paramétereket nem kell majd szegény scriptereknek minden sorban átírni, az megint hülyeség, hisz ki adna értéket olyan paraméternek, amely nincs használva?...Nos mindegy, nem a kritizálásért vagyunk most itt. Ha megnézzük, a kód 8-as shiftelést használ, mivel egy objektum adatait 8 bit tárolja. A mûködési elv ugyanaz, mint az elõzõ encode függvényeknél, így erre nem kívánok megint kitérni.Nézzük azonban hogyan írnánk ehhez egy decode függvényt. Mikor megkérdeztem errõl ismerõsömet, õ azt mondta, ugyanúgy mint az elõzõ, decode_panels függvénynél, csak 8-as shiftelést használunk, és 15 helyett 255-el végezzük a bitenkénti ÉS mûveletet. 255-el? minek?No erre is ki fogok térni késõbb, de haladjunk a szokásos módon. 1 bemeneti paraméterünk van, és 4 kimeneti, vagyis a header rész a következõ: decode_doors(doors, &bonnet, &boot, &driver_door, &passenger_door) Ezzel meg is vagyunk, itt nem látok különösebb magyarázni valót. Én most nem fogok törõdni a 2 nem létezõ ajtóval, ha egy jövõbeni verzióban netán azokat is behoznák mint lekérdezhetõ objektum, akkor ígyis-úgyis módosítani kell majd a kódot, addig meg úgysincs rá szükségünk.Nos, mint az eddigi decode függvényeknél, elsõ feladatunk egy shiftelés mentes bitenkénti ÉS mûvelet elvégzése. Az egyik megoldás a következõ: bonnet = doors & 255; Lehet így is. Azonban véleményem szerint felesleges itt egy ekkora számmal \"szenvednünk\". Miért használjunk ekkora számot, és ezáltal bonyolítsuk feleslegesen a kódunkat, ha nekünk a 8 bitbõl csak az elsõ 3 lesz ami adatot tárol? Bõven elegendõ egy olyan szám, amely bináris alakja 111. Ez nem más, mint a 7. Tehát a következõ megoldás ugyanúgy helyes: bonnet = doors & 7; A két kód pontosan ugyanazt az eredményt fogja visszaadni, semmi különbség a kettõ között. Csupán elsõ esetében nagy számmal dolgozunk, még második verzió esetén csak akkorával, amekkora szükséges.A kód többi részében csak 8-as shifteléssel végigcammogunk a doors változó többi bitjén is: boot = doors >> 8 & 7;driver_door = doors >> 16 & 7;passenger_door = doors >> 24 & 7; A teljes kód egyben: decode_doors(doors, &bonnet, &boot, &driver_door, &passenger_door){ bonnet = doors & 7; boot = doors >> 8 & 7; driver_door = doors >> 16 & 7; passenger_door = doors >> 24 & 7;} A kód pont ugyanazon az elven épül fel, mint az összes többi decode függvényünk, csak épp a benne használatos értékek az \"ajtó\" objectumok adatstruktúrájához vannak igazítva.2.6 Egyéb ötletek gyakorlati alkalmazásra, hasznosításraJoggal felmerülhet a kérdés, hogy ennyi? Emiatt rágtuk végig magunkat ezen néhány tízezer karakteren? Nem. Nem ennyi. A SA:MP-ban rengeteg felhasználási módja lehet még a bitmûveleteknek. Mutatok is néhány dolgot, mire jók még.2.6.1 RGBA->HEX; HEX->RGBA konvertálásokNos, bármily \"hihetetlen\", erre is használható. Miért is? Nos, 16-os számrendszerben 0-9, valamint A-F \"szánjegyek\" léteznek. A hexadecimális számjegy decimális értékben 0-15 lehet. Ha megnézzük bináris értékeket, 0-15 az 4 bittel leírható, hiszen a 15 az bináris alakban 1111. De ha józan paraszti ésszel belegondolunk is. 16 az 24. Így tehát 1 hexadecimális érték, az 4 bináris értékkel írható le. Ebbõl már sejthetõ, hogy itt is shiftelésre lesz szükségünk. Naná Mivel 4 bit ír le egy hexadecimális számjegyet, így 4-esével kell majd shiftelnünk.Mi is az az RGBA?RGBA: A színek olyan meghatározási módja, melyben a 3 alapszín([r]ed, [g]reen, lue) mellett egy 4. érték, az úgynevezett [a]lpha is szerepel, amely az áttetszõséget adja meg. A legtöbb program(pl. paint, PS), és pl. a css(külön függvénnyel!) is elfogadja ezen értékeket decimális számokként(0-255) is, a SA:MP-ban azonban hexadecimális alakban kell megadnunk. Bármily furcsa a következõ 4 sor pont ugyan azt jelenti:\"Normál\" RGB alak(pl. paint, photoshop): Piros: 0, Zöld: 255, Kék: 255CSS alak(függvénnyel): rgba(0, 255, 255, 1);Pawn alak(1): 0x00FFFFFFPawn alak(2): 16777215 Ó igen, a Pawn-ban decimális számokként is megadhatjuk a színeket, hisz a program mindent bináris számrendszerbe fog átszámolni. De akár bináris alakban is megadhattuk volna:Pawn alak(3): 0b111111111111111111111111 Az, hogy mi a Pawn-nak hexadecimális alakban szoktuk megadni, annak az egyetlen oka, hogy így a leginkább \"átlátható\", és ez egy amúgy is elfogadott módja (mármint a hexadecimális számmal való megadás) a színek leírásának a számítástechnikában.Nos, mivel 1 szín 0 és 255 közötti értéket tartalmaz a piros, zöld és kék színekbõl, ez hexadecimálisan 2 számjeggyel leírható: 00-FF. Az FF értéke 255, aki nem hiszi, számoljon utána (162-1)2 hexadecimális számjegy az pedig 8 bináris számjeggyel leírható(00000000-11111111). Így már láthatjuk, hogy 8-as shiftelésre lesz szükségünk. Másra nincs is szükségünk, az RGBA->HEX konverter elkészítéséhez.stock RGBAToHex( r, g, b, a ){return a | b << 8 | g << 16 | r << 24;} Az, hogy miért fordított sorrendben kellett megadnunk az értékeket, az megint csak azzal magyarázható, hogy a biteket hétköznapi értelemben véve visszafelé számozzuk.Egy ellentét, azaz HexToRGBA függvény megírása sem túl bonyolult, ugyan ezt kell csinálnunk, csak visszafelé.stock HexToRGBA( colour, &r, &g, &b, &a ){a = colour & 0xFF;b = (colour >> 8 ) & 0xFF;g = (colour >> 16 ) & 0xFF;r = (colour >> 24 ) & 0xFF;} A 0xFF-et természetesen decimális értékkel is megadhatjuk(255).No akkor lehet ugye RGBA-t ARGB-be is konvertálni, az így néz ki:RGBAToARGBHEX(r,g,b,a){return (r & 0xff) << 24 | (g & 0xff) << 16 | (b & 0xff) << 8 | a & 0xff;} Ez máris egy újabb felhasználási módja a bitmûveleteknek. De, mutatok még 2.6.2 Boolen változók helyett IntegerAz al-al-alcímet elolvasva joggal felmerülhet a kérdés, hogy ez meg hogy jön ide. Nos ez úgy jön ide, hogy most egy kis memóriatakarékosságról fogok itt regélni. Adhattam volna azt is címnek, hogy miért ne használjunk boolean változókat, ha legalább 5 igaz/hamis értéket el akarunk tárolni.Nos akkor egy kis memórialecke. Azt már tisztáztuk, hogy az integer változók 32 bitesek, vagyis 4 bájtot foglalnak le a memóriában maguknak. A boolean típusú változókról azonban még nem volt szó. Mivel 1 értéket tárolnak csak, az is csak igaz vagy hamis lehet, így joggal gondolhatnánk, hogy 1 bit a mérete. Azonban nem...1 boolean változó 8 bitet, azaz 1 bájtot foglal le magának a memóriában. Hogy ez miért így van, az a számítógépek memóriahasználatával magyarázható. Manapság nincs mód arra, hogy a memóriában egyetlen bitet módosítsunk, csak 8-as \"csomagokban\", vagyis bájtonként tudjuk ezt megtenni. Hogy ez miért van így, azt most nem fogom itt fejtegetni Nos, ha megnézzük tehát, a boolean változó egy nagyon-nagyon pazarló megoldás. Hiszen 1 darab boolean változó 1 bájtot foglal, 4 darab boolean változó esetén ez már 4 bájt, ami megegyezik 1 integer változó méretével. Azonban, még a 4 boolean változóban 4 igaz/hamis érték kerülhet tárolásra, addig egy integerben 32 igaz/hamis értéket is eltárolhatunk.Bizony, 32 igaz/hamis érték. Hiszen 1 integer változó 32 bitbõl áll, ha minden bitet hasznosítunk, akkor a bitmûveletek felhasználásával 32 igaz/hamis értéket is eltárolhatunk. Összehasonlítva:32 darab boolean változó mérete 32 bájt.1 integer változó mérete: 4 bájt. Vagyis 32 boolean változó esetén nyolcad annyi memóriánkba kerül, ha egy darab integerben tároljuk el az értékeket. Persze 32 booleanra ritkán van szükség, de egy RP modban simán elõfordulhat. Gondoljunk csak bele... Van 32 boolean-tömbös változónk, ez a maximum slotszámot(1000) figyelembe véve: 32*1000 byte azaz 32000 byte ami ~31 kilobyte. Ha ezt egyetlen integer-tömbbel oldanánk meg: 4*1000 byte, azaz 4000 byte, ami ~3,9 kilobyte. Azért így nézve már nem annyira \"elhanyagolandó\" az a különbség szerintem =)Akár írhatnánk erre külön egy függvényt, mely az adott bit értékével tér vissza. Nem is bonyolult. Így már azok számára is használhatóak a bitmûveletek, akik nem értenek hozzá, és rengeteg memóriát takaríthatnak meg.Íme egy függvény az adatok kiolvasására:stock GetData(integer, bit){ return integer >> bit & 1; //vagy az 1 helyett akár 0x01 alakban is megadhatjuk, nincs jelentõsége} És a párja, adat írásra:stock SetData(&integer, bit, value){ integer |= value << bit; return 1;} Persze ezt még lehetne csinosítani, hogy pl. ne 0-tól hanem 1-tõl számoljuk a biteket, vagy hogy 31(vagy 32 ha 1-tõl számolunk) felett 0 legyen a return értéke, esetleg a value-re is rakhatnánk ellenõrzést, hogy 0-1-e az értéke, vagy más, de ilyenekkel most nem törõdtem.2.6.3 Több érték, egy integerbeNa ez az a rész, ami véleményem szerint már csak a nagyon megrögzött memóriafelszabadítók szerint nem lesz felesleges Amennyiben nincs szükségünk túl nagy számokra(pl. csak 0-15, vagy akár csak 0-7, de akár még a 0-255 is megfelel), akkor akár egy integerbe is tehetjük õket. Hogyan? Egyszerû. Csoportosítjuk a biteket. Tegyük fel, hogy maradván egy RP szervernél, el akarjuk tárolni ûbergigantikusan memóriakímélõ megoldással a játékos házának számát(ez legyen mondjuk 0-100 közötti szám), a játékos frakcióját(ez legyen 0-15 között), a játékos rangját(0-7 között), hogy a telefonja be van-e kapcsolva(0-1), a játékos szolgálati jármûvének id-jét(legyen 0-255 között), és az alapmunkáját(0-15 között). Ez is megoldható, egyetlen integerben, és ismételten 1000 slottal számolva máris megspóroltunk 4*4*1000+1*1000 byte-t(5 integer, és 1 boolean helyett csupán 1 integert használunk), ami ~16,6 kilobyte. Azért nem mindegy az, hogy 20,5 kb-ot vagy 4 kb-ot használunk szerintem... Ezt a következõ módszerrel valósíthatnánk meg:#define DATA_HOUSE 0#define DATA_FACTION 1#define DATA_RANK 2#define DATA_PHONESTATE 3#define DATA_DUTYCAR 4#define DATA_JOB 5stock GetData(playerinfo, data){ switch(data) { case DATA_HOUSE: return playerinfo & 127;//7 bit case DATA_FACTION: return playerinfo >> 7 & 15;//4 bit case DATA_RANK: return playerinfo >> 11 & 7;//3 bit case DATA_PHONESTATE: return playerinfo >> 14 & 1;//1 bit case DATA_DUTYCAR: return playerinfo >> 15 & 255;//8 bit case DATA_JOB: return playerinfo >> 23 & 15;//4 bit}return -1;}stock SetData(&playerinfo, data, value){ switch(data) { case DATA_HOUSE: { playerinfo = playerinfo & ~127 | value; }//7 bit (0-127 közötti maximálisan) case DATA_FACTION: { playerinfo = playerinfo & ~(15 << 7) | value << 7; }//4 bit (0-15 között) case DATA_RANK: { playerinfo = playerinfo & ~(7 << 11) | value << 11; }//3 bit (0-7 között) case DATA_PHONESTATE: { playerinfo = playerinfo & ~(1 << 14) | value << 14; }//1 bit (0-1) case DATA_DUTYCAR: { playerinfo = playerinfo & ~(255 << 15) | value << 15; }//8 bit (0-255 között) case DATA_JOB: { playerinfo = playerinfo & ~(15 << 23) | value << 23; }//4 bit (0-15 között) } return -1;}stock SetDatas(&playerinfo, house, faction, rank, phonestate, dutycar, job){playerinfo |= house | (faction << 7) | (rank << 11) | (phonestate << 14) | (dutycar << 15) | (job << 23);} Ez már azért elég morbid, ezt inkább csak érdekességképp mutattam, de éppenséggel ez is használható lenne, csak ehhez már kell egy kis õrültség 2.7 Második fejezet végeNos, láthattuk hogyan is mûködik mindazon sok \"marhaság\", amit az elsõ fejezetben elméletként átvettünk. Láthattuk hogyan mûködnek a jármûvek paneljének adataival babráló encode függvények, valamint megmutattam, hogyan írhatunk hozzájuk decode verziókat. Láthattunk még példát egyéb gyakorlati hasznosításra, kicsit megkoccoltuk a hexadecimális számok világát is, majd szóba került egy kis memóriatakarékosság. Végül mutattam egy összetettebb, elvetemült példát is, a bitmûveletek hasznosítására.3. Összegzés, végszó Úgy gondolom, hogy elég részletesen körbe jártuk a témát. Megnéztük a bitmûveletek elméleti, és gyakorlati oldalát is, láthattunk gyakorlati példákat. Nem kell elsõre megérteni ezt az egészet, sõt... Elegendõ ha egyszer elolvassuk, emésztjük kicsit, majd késõbb megint elõvesszük, akkor netán már kicsit többet is megértünk belõle. Nem egy kihagyhatatlan része a Pawno-nak, de azért van annak haszna, ha az ember jártas ebben a témában is, bár sokak szerint nem éri meg a sok fáradtságot, és idõt, mely a megértéséhez, begyakorlásához, használatának elsajátításához szükséges. Próbáltam mindent részletesen kifejteni. A bonyolultabb dolgokhoz sokat magyarázni, az egyszerûbbek esetén csak visszautalni az alapokhoz. Sok dolgot talán már túl is magyaráztam. Ezt a leírást nem 1 nap alatt készítettem el, kb. 1 hónapja írtam meg az elméleti oldalát, most az elmúlt pár napban pedig a gyakorlatot. Esténként 1-2 órát szántam rá csupán, és mivel ilyenkor már nem vagyok épp a toppon, elképzelhetõ, hogy néhány helyen helyesírási, vagy elvi hibát vétettem. Amennyiben valaki ilyet találna, kérem szóljon, és korrigálni fogom. Nos, azt hiszem nincs is más hátra, mint hogy sok szerencsét kívánjak a bitmûveletek elsajátításához, és megosszam a forrásokat. Remélem néhányan hasznosnak találják, eme igen hosszúra sikerült leírást.4. Források: Internet:WikipédiaSA:MP WikiSA:MP hivatalos fórumKönyv, e-book:Pohl László - A programozás alapjai(BME - Elektronikus Eszközök Tanszéke, Budapest, 2010)Siroki László - C programozás - Bitmûveletek jegyzeteˆ Anthony, 2012 - Minden jog fenntartva.A leírás egészben, vagy részleteiben szabadon terjeszthetõ, ameddig egyértelmûen fel van tüntetve az eredeti készítõ, valamint hogy honnan is származik. « Utoljára szerkesztve: 2012. augusztus 01. - 10:53:54 írta Anthony » Naplózva
11111111111111111111111111111000//-8<<211111111111111111111111111100000//-32[/quote]Láthatjuk hogy a balra shifteléskor a szám mindig 0-kkal egészítõdik ki, legyen az akár negatív, akár pozitív. Nem veszi figyelembe az MSB értékét, így ha túl sokat shiftelünk, akkor elõfordulhat, hogy a szám elveszti negatív értékét, és eléri a 0-t.1.4.6 Logikai biteltolás (logical shift, logikai shiftelés) operátorElõször is le kell tisztázni, hogy logikai shiftelésbõl csak egy létezik, jobbra logikai shiftelés(>>>). Sokban hasonlít a jobbra shiftelõ operátorra (>>), azonban van egy fontos különbség.Nézzük az elsõ példát: 00000000000000000000000000001000//8>>>200000000000000000000000000000010//2[/quote]Eddig nem igazán tapasztalhattunk semmi változást, minden pont ugyanúgy zajlott, mint a jobbra shiftelésnél(>>).Nézzük mi történik negatív szám esetén: 11111111111111111111111111111000//-8>>>200111111111111111111111111111110 //1073741822[/quote]Itt már láthatjuk a különbséget. Logikai shiftelés esetén az üres bitek helyére bekerülõ érték nem függ az MSB-tõl, hanem mindig 0. Emiatt soha nem kaphatunk eredményül negatív számot, logikai shiftelés esetén.1.5 Elsõ fejezet végeNos, ezennel átvettünk mindent, amit érdemes tudni a bitmûveletekrõl. Megmutattam az alapok, hogy hogyan épülnek fel a változók, valamint átvettük a fõ operátorokat. Eddig csak színtiszta elmélet volt. A következõ részben megmutatom, hogyan hasznosíthatjuk mindezt a gyakorlatban, vagyis mire is jó az a sok marhaság, amit eddig leírtam.2. Gyakorlati példák, érdekességek 2.1 Áttekintés, mikrõl is lesz még szóAz alábbiakban még pár dolgot szeretnék mutatni. Elõször is néhány \"függvénnyel\" fogok foglalkozni, majd mutatok még egy-két érdekességet. Konkrétan megmutatom, hogyan tudjuk egy jármû minden \"paneljének\" adatát kiíratni, részletesen(erre alapból nincs lehetõség, és a függvények is mind \"encode\" függvények, én viszont mutatok egy-egy decode \"verziót\" is.)Az alábbi 4 függvénnyel fogok foglalkozni: encode_tiresencode_panelsencode_doorsencode_lightsDe elõbb egy kis háttérinfó:A Pawnban a forrásban megadhatunk bináris számokat ugyanúgy, ahogyan Hex számokat is megadhatunk. Ehhez a \"0b\" prefixet kell használnunk. Tehát ha bináris számunk az 1011 és ezt akarjuk adni értékül egy változónak, azt így tehetjük meg: new var = 0b1011; 2.2 encode_tiresHáttér információ:A SA:MP-ban egy jármû abroncsainak adataid 4 bit tárolja. Az elsõ bit a jobb hátsó, a második bit a jobb elsõ, a harmadik bit a bal hátsó, a negyedik bit pedig a bal elsõ abroncsról tárol információt. Mivel 4 bitben eltárolhatóak az információk, így decimális formában 0-15 értékekkel is be lehet állítani a jármûvek abroncsait. 16-tól kezdve az értékek már ismétlõdni kezdenek, szóval a 16-nál az eredmény ugyanaz mint 0-nál, 17-nél ugyanaz mind 1-nél, és így tovább. Ez érthetõ, ha tudjuk hogyan épül fel a kettes számrendszer.(Ne felejtsük el, a biteket hátulról számozzuk!)Nos nézzük hogyan néz ki a kód: encode_tires(tire1, tire2, tire3, tire4){ return tire1 | tire2 << 1 | tire3 << 2 | tire4 << 3;} Láthatjuk, hogy bekérjük a 4 abroncs adatát(vagyis hogy ép[0], vagy kidurrant[1]), majd egy sima bitenkénti VAGY mûvelet és biteltolás kombinálásával visszatérünk az eredménnyel. A kódban a tire1 felel meg a jobb hátsó abroncsnak, a tire2 a jobb elsõ, a tire3 a bal hátsó, és a tire4 a bal elsõnek. Nézzünk egy konkrét példát. Tegyük fel, hogy én a hátsó 2 abroncsot ki akarom durrantani, még az elsõ kettõt épre akarom beállítani. Ez esetben így néz ki a dolog: encode_tire(1,0,1,0); Ha kiíratnánk konzolba a visszatérési értéket (%d-vel), 5-öt kapnánk. Miért is? Az 5 bináris alakja 0101. Hoppá, ha megnézzük ez pont fordítottja annak, amit mi megadtunk számjegyekként, csak már konkrét bináris számként. Nézzük hogyan is történhetett mindez, mit is csináltunk. A függvény a következõ mûveletet végzi: tire1 | tire2 << 1 | tire3 << 2 | tire4 << 3 Konyhanyelven úgy magyarázhatnánk el a dolgot, hogy ez a kód egymás mellé pakolgatja a megadott számokat, fordított sorrendben. Na de mi most nem a konyhanyelv miatt vagyunk ebben a leírásban, szóval nézzük a kódunkat. Lényegében ez futott le, a konkrét értékeinkkel nézve: 1 | 0 << 1 | 1 << 2 | 0 << 3 Bármily furcsa is, ez egy mûvelet, melyen szépen végig halad sorban a program. Elõször is írjuk át az egészet bináris alakba(az egyszerûség kedvéért most csak 4 bitet fogok kiírni): 0001 | 0000 << 1 | 0001 << 2 | 0000 << 3 Na erre se mondtuk volna rá a tutorial elsõ részének elolvasása elõtt, hogy ez egy mûveletsor. A rendszer elsõnek az eltolásokat végzi el (olyan ez mint a 2*3+2*4+2*5 mûveletben elõször a szorzásokat végezzük el, és csak utána adunk össze, ha nincs zárójel), és csak utána a bitenkénti VAGY mûveleteket, ezért mi is így fogunk tenni. Emlékezzünk, eltoláskor az \"üres\" bitek 0 értéket fognak felvenni. Végezzük el külön csak az eltolásokat: 0000 << 1 = 00000001 << 2 = 01000000 << 3 0000 Nos, ha ezeket elvégeztük, akkor nézzük, hogy is áll most a kódunk: 0001 | 0000 | 0100 | 0000 Négy azonos mûvelet van hátra, így hát a program innen más szépen sorban fog haladni. Tegyünk mi is így. Elsõ tehát a 0001 | 0000 mûvelet elvégzése. Emlékeztetõül, a bitenkénti VAGY mûvelet akkor ad 0-t eredményül, ha mind a kettõ érték 0, minden más esetben 1-et. Végezzük el külön az elsõ bitenkénti VAGY mûveletet: 0001 | 0000 = 0001 Nem volt bonyolult, nézzük tehát hogy áll akkor most a teljes mûveletünk: 0001 | 0100 | 0000 Haladunk szépen tovább, elvégezzük a 0001 | 0100 mûveletet. 0001 | 0100 = 0101 Nincs mit magyarázni rajta, egyszerû bitenkénti VAGY mûvelet volt ez is. Már csak ez van hátra: 0101 | 0000 Nos, ez sem lesz egy bonyolult mûvelet: 0101 | 0000 = 0101 Ezzel meg is kaptuk a végeredményt, ami nem más, mint a 0101 (tízes számrendszerben 5). Mivel azonban eléggé kusza lett így a számításunk, nézzük egyben hogyan is néz ki az egész, ha nem bontom ennyire részekre: 0100 | 0000 << 1 | 0001 << 2 | 0000 << 3v0001 | 0000 | 0100 | 0000v0001 | 0100 | 0000v0101 | 0000v0101 Nos, ezennel láthattuk a gyakorlatban is, hogyan mûködik a függvény. Kijelenthetjük tehát, hogy a függvény a számokat a sorrendjüknek megfelelõ számú bitbe helyezi. Mivel azonban a biteket hátulról számozzuk, így az 1,0,1,0 bemenetek végeredménye 0101 lesz. Így már nem is olyan bonyolult Na, ha már ilyen sokáig eljutottunk, akkor felhasználhatjuk ezt a kódot arra, hogy az alapjait figyelembe véve elkészítsük a decode változatot. Mivel 4 értékkel térünk vissza, ezért paraméterként kell majd 4 változó, melyeknek módosíthatjuk az értékét, illetve 1 paraméter pedig a dekódolandó szám. A függvényünk \"címsora\" tehát így néz ki: decode_tires(tires, &tire1, &tire2, &tire3, &tire4) Hogy példánk ne csak elmélet legyen, a tires változó legyen az elõbbi 0101 értékkel egyenlõ. Na akkor menjünk tovább. Bemeneti értékünk ugye a \"tires\" változóban lesz eltárolva, így ezzel fogjuk a mûveleteket végezni. Kezdjünk el gondolkozni. Hogyan is tudnánk ellenõrizni egy adatot. Legegyszerûbb ha a bitenkénti ÉS mûveletet alkalmazzuk. Miért is? Nos, a bitenkénti ÉS mûvelet ugye akkor ad eredményül 1-et, ha mind a 2 érték egy. Tehát ha min egy számot összehasonlítunk a 1-el(binárisan ugye 00000000000000000000000000000001, akkor az eredményünk lényegében arról fog tájékoztatni, hogy az elsõ bit 0, vagy 1. Nem is olyan bonyolult ez. Nézzük akkor az elsõ bitet. Arra emlékezhetünk még, hogy az elsõ bit a tire1, vagyis jobb hátsó abroncs adatait tárolja. Tehát: tire1 = tires & 1; Vagy konkrétan a példánk esetében: tire1 = 0101 & 0001 Mivel a bitenkénti ÉS mûvelet akkor ad 1-et eredményül, ha mind a kettõ bit 1, akkor már láthatjuk is, hogy miért elegendõ az elsõ bitet szemügyre venni. Mivel mind a 2 helyen az elsõ bit 1, ezért a tire1 változónk értéke is 1(0001) lesz. Egyszerû és nagyszerû, máris megkaptuk a jobb hátsó abroncs állapotát.Gondolkozzunk hát tovább. Nekünk most arra van szükségünk, hogy a 2. bit értékét megkapjuk. Hogyan kaphatjuk ezt meg? Legegyszerûbb megoldás, ha a tires változót jobbra shifteljük 1-el, majd ismételten bitenkénti ÉS mûveletet végzünk. Nézzük tehát, hogyan is nézne ez ki: tire2= tires >> 1 & 1; Vagy konkrétan a mi példánkban: tire2 = 0101 >> 1 & 0001vtire2 = 0010 & 0001 Ismételten bitenkénti ÉS mûvelet, és az eredményünk 0, mivel csak az egyik bit 1, a másik 0, így a bitenkénti ÉS mûvelet eredménye is 0. Ezen az elven megcsináljuk a maradék 2 abronccsal is: tire3= tires >> 2 & 1;tire4= tires >> 3 & 1; Vagy a mi esetünkben konkrét példánkkal: tire3 = 0101 >> 2 & 0001 // azaz: --> 0001 & 0001 = 1tire4 = 0101 >> 3 & 0001 // azaz: --> 0000 & 0001 = 0 A teljes kódunk tehát: decode_tires(tires, &tire1, &tire2, &tire3, &tire4){ tire1 = tires & 1; tire2 = tires >> 1 & 1; tire3 = tires >> 2 & 1; tire4 = tires >> 3 & 1;} A konkrét példánkban a 4 változónk értéke a függvény lefutása után: tire1: 1tire2: 0tire3: 1tire4: 0 Ha összehasonlítjuk, ezek pont azok az értékek, mint amiket az encode függvényben megadtunk, tehát úgy tûnik jól dolgoztunk 2.3 encode_lightsEz a rész lesz a legrövidebb. Az egész lámpás rész pontosan ugyanarra a sémára épül mint az abroncsok. Az adott bitek ugyanazon a \"helyen\" lévõ lámpa adatait szolgáltatják, ugyanúgy 0 az ép, és 1 a sérült lámpák állapot azonosítója, és ugyanúgy 1-1 bit tárolja az értékeket.Maga a kód: encode_lights(light1, light2, light3, light4){ return light1 | light2 << 1 | light3 << 2 | light4 << 3;} Láthatjuk hogy halál pontosan ugyanaz mint az encode_tires, csak más változónevekkel. Így hát a decode függvény is ugyan az lesz, csak a változóneveket kell módosítanunk: decode_lights(lights, &light1, &light2, &light3, &light4){ light1 = lights & 1; light2 = lights >> 1 & 1; light3 = lights >> 2 & 1; light4 = lights >> 3 & 1;} Ha megnézzük látjuk, hogy teljesen ugyan az a séma, ugyan az a felépítés, minden megegyezik, ezért nem is írtam le újból ugyanazt, amit egyszer már kifejtettem.2.4 encode_panelsHáttér információ:A SA:MP 7 panelrõl tárol adatokat. Egy panel adatait 4 bit írja le. Nos, ennél több infóra nekünk most nincs szükségünk, az elõzõ kód alapján ezen kód is megérthetõ, így az encode verzióhoz nem is nagyon szeretnék magyarázatot fûzni. A különbség az encode_tires-hez képest, hogy itt 4-essével shiftelünk, mivel 1 panel adatait 4 bit tárolja.Maga a kód: encode_panels(flp, frp, rlp, rrp, windshield, front_bumper, rear_bumper){ return flp | frp << 4 | rlp << 8 | rrp << 12 | windshield << 16 | front_bumper << 20 | rear_bumper << 24;} Nézzünk egy decode verziót, ehhez a kódhoz. Alapjaiban ugyanazon a vonalon kell elindulnunk, mint a decode_tires esetében. Itt most 7 visszatérési értékünk lesz, és 1 bemeneti érték. Tehát így néz ki a függvény header: decode_panels(panels, &flp, &frp, &rlp, &rrp, &windshield, &front_bumper, &rear_bumper) A folytatásban megint csak az elõbbi verziót kell alapul vennünk, azonban itt lesz egy kis különbség. Egyértelmû, hogy mivel 1 panel adatait 4 bit tárolja, így a 4 bitet nekünk egyben kell lekérni, és egyben kell vele visszatérnünk. Megint csak a bitenkénti ÉS mûveletet fogjuk alkalmazni, azonban mivel 4 bitrõl van szó, így egy olyan számmal kell összehasonlítanunk, mely bináris alakban 1111. Ez a szám nem más, mint a 15. Hogy miért ez kell?Ha egy bitsorozat tartalmát ki szeretnénk másolni egy másik változóba, akkor a bitenkénti ÉS mûveletre vna szükségünk. A bitenkénti ÉS mûvelet akkor ad 1-et eredményül, ha mindkét bit 1, ha valamelyik 0, akkor a visszatérés már 0 lesz. Ergó tehát, ha egy csupa 1-esekbõl álló bitsorozattal vetjük össze az eredeti bitsorozatunkat, akkor lényegében lemásoljuk a sorozatot, egy másik változóba, hisz eredményül mindig az eredeti sozatot fogjuk visszakapni. Íme néhány példa: 0101 & 1111 = 01011111 & 1111 = 11110000 & 1111 = 00001011 & 1111 = 1011 Láthatjuk tehát, hogy ha csupa 1-est tartalmazó bitsorozattal hasonlítjuk össze az eredeti számunkat, akkor lényegében egy másolatot készítünk. Mivel nekünk erre a 4 bitre van szükségünk a 32-bõl, így pontosan ezt kell tennünk, ezen 4 bitet lemásolni egy másik változóba, hogy késõbb ezt majd egy másik függvénnyel még jobban darabokra szedhessük.Íme tehát akkor a kódunk elsõ sora: flp = panels & 15; Mivel 4-esével vizsgáljuk a biteket, így lényegében annyit kell még változtatnunk az elõzõ kódon(decode_tires), hogy az 1-esével növekvõ shiftelések helyett 4-esével shifteljünk. Azaz a 2. panel adatainak lekérdezése: frp = panels >> 4 & 15; Innen pedig már csak mindig további 4 shiftelést el kell végeznünk, és szépen végig haladhatunk a panels változó bitjein, és megkaphatjuk minden panel adatait külön-külön. rlp = panels >> 8 & 15;rrp = panels >> 12 & 15;windshield = panels >> 16 & 15;front_bumper= panels >> 20 & 15;rear_bumper = panels >> 24 & 15; Ezennel minden panel adatait megkaptuk. Ezután már akár a decode_tires függvényt felhasználva szét lehet szedni az adatokat darabokra, de akár egy saját névvel, és más változó nevekkel ellátott másik függvényt is létre lehet hozni a decode_tires mintájára.Tehát az egész kód egyben: decode_panels(panels, &flp, &frp, &rlp, &rrp, &windshield, &front_bumper, &rear_bumper){ flp = panels & 15; frp = panels >> 4 & 15; rlp = panels >> 8 & 15; rrp = panels >> 12 & 15; windshield = panels >> 16 & 15; front_bumper= panels >> 20 & 15; rear_bumper = panels >> 24 & 15;} Igazából a függvény ugyanarra a sémára épül mint a decode_tires, csak annyi a különbség, hogy 4 bitenként kapjuk az adatokat, nem pedig egyesével, mivel itt minden panelnek 4 adatát tudjuk majd még lekérdezni. Nem tudom lehet-e még ehhez a részhez hozzáfûzni valamit, szóval szerintem ugorjunk. 2.5 encode_doorsHáttér információ:A SA:MP képes minden jármû 4 \"ajtajának\" állapotát tárolni. Ezek az \"ajtók\"(Doors-t hogy fordíthatnám jobban?) konkrétan a motorháztetõ, csomagtartó, vezetõoldali ajtó, és anyósülés oldali ajtó. A 2 hátsó ajtó állapotát jelenleg még nincs lehetõségünk lekérdezni.Amikor lekérdezzük egy jármû \"ajtajának\" adatait, azt a SA:MP 4 bájtként(32 bit) adja vissza. Ebbõl a 4 bájtból 1 bájt(8 bit) jut minden egyes objektumra. Vagyis minden egyes objektum adatait 8 bit tárolja. Vagyis tárolhatná...A SA:MP csak az elsõ 3 bitet használja, a további bitek üresek lesznek, nincs funkciójuk, talán majd késõbb lesz. Az elsõ bit azt tárolja, hogy az objektum zárt[0], vagy nyitott[1](nem az jelenti, hogy bezárt, hanem becsukott/kinyitott) állapotban van-e. A második bit azt adja meg, hogy az objektum ép[0] vagy sérült[1]. A 3. bit arról tájékoztat, hogy az adott objektum rajta van-e még a jármûvön[0] vagy már leesett[1]. A többi bit mint mondtam üres. Ez majd a decode függvénynél lesz fontos.Nézzük tehát a konkrét kódot: encode_doors(bonnet, boot, driver_door, passenger_door, behind_driver_door, behind_passenger_door){ #pragma unused behind_driver_door #pragma unused behind_passenger_door return bonnet | boot << 8 | driver_door << 16 | passenger_door << 24;} A 2 hátsó ajtóval nem foglalkozunk, mivel azok jelenleg még nem támogatottak ugyebár, de a kód készítõje gondolt a jövõre is, vagy fogalmam sincs miért adta meg ezt a két paramétert is, hisz ígyis-úgyis módosítani kell a kódon... Ha behoznak még 2 ajtót, akkor az 1 bájtos tárolással már 6 bájt lenne, de 1 változó maximum 4 bájt lehet, tehát a kódot úgyis módosítani kell, ha meg arra gondoltak, hogy legalább a paramétereket nem kell majd szegény scriptereknek minden sorban átírni, az megint hülyeség, hisz ki adna értéket olyan paraméternek, amely nincs használva?...Nos mindegy, nem a kritizálásért vagyunk most itt. Ha megnézzük, a kód 8-as shiftelést használ, mivel egy objektum adatait 8 bit tárolja. A mûködési elv ugyanaz, mint az elõzõ encode függvényeknél, így erre nem kívánok megint kitérni.Nézzük azonban hogyan írnánk ehhez egy decode függvényt. Mikor megkérdeztem errõl ismerõsömet, õ azt mondta, ugyanúgy mint az elõzõ, decode_panels függvénynél, csak 8-as shiftelést használunk, és 15 helyett 255-el végezzük a bitenkénti ÉS mûveletet. 255-el? minek?No erre is ki fogok térni késõbb, de haladjunk a szokásos módon. 1 bemeneti paraméterünk van, és 4 kimeneti, vagyis a header rész a következõ: decode_doors(doors, &bonnet, &boot, &driver_door, &passenger_door) Ezzel meg is vagyunk, itt nem látok különösebb magyarázni valót. Én most nem fogok törõdni a 2 nem létezõ ajtóval, ha egy jövõbeni verzióban netán azokat is behoznák mint lekérdezhetõ objektum, akkor ígyis-úgyis módosítani kell majd a kódot, addig meg úgysincs rá szükségünk.Nos, mint az eddigi decode függvényeknél, elsõ feladatunk egy shiftelés mentes bitenkénti ÉS mûvelet elvégzése. Az egyik megoldás a következõ: bonnet = doors & 255; Lehet így is. Azonban véleményem szerint felesleges itt egy ekkora számmal \"szenvednünk\". Miért használjunk ekkora számot, és ezáltal bonyolítsuk feleslegesen a kódunkat, ha nekünk a 8 bitbõl csak az elsõ 3 lesz ami adatot tárol? Bõven elegendõ egy olyan szám, amely bináris alakja 111. Ez nem más, mint a 7. Tehát a következõ megoldás ugyanúgy helyes: bonnet = doors & 7; A két kód pontosan ugyanazt az eredményt fogja visszaadni, semmi különbség a kettõ között. Csupán elsõ esetében nagy számmal dolgozunk, még második verzió esetén csak akkorával, amekkora szükséges.A kód többi részében csak 8-as shifteléssel végigcammogunk a doors változó többi bitjén is: boot = doors >> 8 & 7;driver_door = doors >> 16 & 7;passenger_door = doors >> 24 & 7; A teljes kód egyben: decode_doors(doors, &bonnet, &boot, &driver_door, &passenger_door){ bonnet = doors & 7; boot = doors >> 8 & 7; driver_door = doors >> 16 & 7; passenger_door = doors >> 24 & 7;} A kód pont ugyanazon az elven épül fel, mint az összes többi decode függvényünk, csak épp a benne használatos értékek az \"ajtó\" objectumok adatstruktúrájához vannak igazítva.2.6 Egyéb ötletek gyakorlati alkalmazásra, hasznosításraJoggal felmerülhet a kérdés, hogy ennyi? Emiatt rágtuk végig magunkat ezen néhány tízezer karakteren? Nem. Nem ennyi. A SA:MP-ban rengeteg felhasználási módja lehet még a bitmûveleteknek. Mutatok is néhány dolgot, mire jók még.2.6.1 RGBA->HEX; HEX->RGBA konvertálásokNos, bármily \"hihetetlen\", erre is használható. Miért is? Nos, 16-os számrendszerben 0-9, valamint A-F \"szánjegyek\" léteznek. A hexadecimális számjegy decimális értékben 0-15 lehet. Ha megnézzük bináris értékeket, 0-15 az 4 bittel leírható, hiszen a 15 az bináris alakban 1111. De ha józan paraszti ésszel belegondolunk is. 16 az 24. Így tehát 1 hexadecimális érték, az 4 bináris értékkel írható le. Ebbõl már sejthetõ, hogy itt is shiftelésre lesz szükségünk. Naná Mivel 4 bit ír le egy hexadecimális számjegyet, így 4-esével kell majd shiftelnünk.Mi is az az RGBA?RGBA: A színek olyan meghatározási módja, melyben a 3 alapszín([r]ed, [g]reen, lue) mellett egy 4. érték, az úgynevezett [a]lpha is szerepel, amely az áttetszõséget adja meg. A legtöbb program(pl. paint, PS), és pl. a css(külön függvénnyel!) is elfogadja ezen értékeket decimális számokként(0-255) is, a SA:MP-ban azonban hexadecimális alakban kell megadnunk. Bármily furcsa a következõ 4 sor pont ugyan azt jelenti:\"Normál\" RGB alak(pl. paint, photoshop): Piros: 0, Zöld: 255, Kék: 255CSS alak(függvénnyel): rgba(0, 255, 255, 1);Pawn alak(1): 0x00FFFFFFPawn alak(2): 16777215 Ó igen, a Pawn-ban decimális számokként is megadhatjuk a színeket, hisz a program mindent bináris számrendszerbe fog átszámolni. De akár bináris alakban is megadhattuk volna:Pawn alak(3): 0b111111111111111111111111 Az, hogy mi a Pawn-nak hexadecimális alakban szoktuk megadni, annak az egyetlen oka, hogy így a leginkább \"átlátható\", és ez egy amúgy is elfogadott módja (mármint a hexadecimális számmal való megadás) a színek leírásának a számítástechnikában.Nos, mivel 1 szín 0 és 255 közötti értéket tartalmaz a piros, zöld és kék színekbõl, ez hexadecimálisan 2 számjeggyel leírható: 00-FF. Az FF értéke 255, aki nem hiszi, számoljon utána (162-1)2 hexadecimális számjegy az pedig 8 bináris számjeggyel leírható(00000000-11111111). Így már láthatjuk, hogy 8-as shiftelésre lesz szükségünk. Másra nincs is szükségünk, az RGBA->HEX konverter elkészítéséhez.stock RGBAToHex( r, g, b, a ){return a | b << 8 | g << 16 | r << 24;} Az, hogy miért fordított sorrendben kellett megadnunk az értékeket, az megint csak azzal magyarázható, hogy a biteket hétköznapi értelemben véve visszafelé számozzuk.Egy ellentét, azaz HexToRGBA függvény megírása sem túl bonyolult, ugyan ezt kell csinálnunk, csak visszafelé.stock HexToRGBA( colour, &r, &g, &b, &a ){a = colour & 0xFF;b = (colour >> 8 ) & 0xFF;g = (colour >> 16 ) & 0xFF;r = (colour >> 24 ) & 0xFF;} A 0xFF-et természetesen decimális értékkel is megadhatjuk(255).No akkor lehet ugye RGBA-t ARGB-be is konvertálni, az így néz ki:RGBAToARGBHEX(r,g,b,a){return (r & 0xff) << 24 | (g & 0xff) << 16 | (b & 0xff) << 8 | a & 0xff;} Ez máris egy újabb felhasználási módja a bitmûveleteknek. De, mutatok még 2.6.2 Boolen változók helyett IntegerAz al-al-alcímet elolvasva joggal felmerülhet a kérdés, hogy ez meg hogy jön ide. Nos ez úgy jön ide, hogy most egy kis memóriatakarékosságról fogok itt regélni. Adhattam volna azt is címnek, hogy miért ne használjunk boolean változókat, ha legalább 5 igaz/hamis értéket el akarunk tárolni.Nos akkor egy kis memórialecke. Azt már tisztáztuk, hogy az integer változók 32 bitesek, vagyis 4 bájtot foglalnak le a memóriában maguknak. A boolean típusú változókról azonban még nem volt szó. Mivel 1 értéket tárolnak csak, az is csak igaz vagy hamis lehet, így joggal gondolhatnánk, hogy 1 bit a mérete. Azonban nem...1 boolean változó 8 bitet, azaz 1 bájtot foglal le magának a memóriában. Hogy ez miért így van, az a számítógépek memóriahasználatával magyarázható. Manapság nincs mód arra, hogy a memóriában egyetlen bitet módosítsunk, csak 8-as \"csomagokban\", vagyis bájtonként tudjuk ezt megtenni. Hogy ez miért van így, azt most nem fogom itt fejtegetni Nos, ha megnézzük tehát, a boolean változó egy nagyon-nagyon pazarló megoldás. Hiszen 1 darab boolean változó 1 bájtot foglal, 4 darab boolean változó esetén ez már 4 bájt, ami megegyezik 1 integer változó méretével. Azonban, még a 4 boolean változóban 4 igaz/hamis érték kerülhet tárolásra, addig egy integerben 32 igaz/hamis értéket is eltárolhatunk.Bizony, 32 igaz/hamis érték. Hiszen 1 integer változó 32 bitbõl áll, ha minden bitet hasznosítunk, akkor a bitmûveletek felhasználásával 32 igaz/hamis értéket is eltárolhatunk. Összehasonlítva:32 darab boolean változó mérete 32 bájt.1 integer változó mérete: 4 bájt. Vagyis 32 boolean változó esetén nyolcad annyi memóriánkba kerül, ha egy darab integerben tároljuk el az értékeket. Persze 32 booleanra ritkán van szükség, de egy RP modban simán elõfordulhat. Gondoljunk csak bele... Van 32 boolean-tömbös változónk, ez a maximum slotszámot(1000) figyelembe véve: 32*1000 byte azaz 32000 byte ami ~31 kilobyte. Ha ezt egyetlen integer-tömbbel oldanánk meg: 4*1000 byte, azaz 4000 byte, ami ~3,9 kilobyte. Azért így nézve már nem annyira \"elhanyagolandó\" az a különbség szerintem =)Akár írhatnánk erre külön egy függvényt, mely az adott bit értékével tér vissza. Nem is bonyolult. Így már azok számára is használhatóak a bitmûveletek, akik nem értenek hozzá, és rengeteg memóriát takaríthatnak meg.Íme egy függvény az adatok kiolvasására:stock GetData(integer, bit){ return integer >> bit & 1; //vagy az 1 helyett akár 0x01 alakban is megadhatjuk, nincs jelentõsége} És a párja, adat írásra:stock SetData(&integer, bit, value){ integer |= value << bit; return 1;} Persze ezt még lehetne csinosítani, hogy pl. ne 0-tól hanem 1-tõl számoljuk a biteket, vagy hogy 31(vagy 32 ha 1-tõl számolunk) felett 0 legyen a return értéke, esetleg a value-re is rakhatnánk ellenõrzést, hogy 0-1-e az értéke, vagy más, de ilyenekkel most nem törõdtem.2.6.3 Több érték, egy integerbeNa ez az a rész, ami véleményem szerint már csak a nagyon megrögzött memóriafelszabadítók szerint nem lesz felesleges Amennyiben nincs szükségünk túl nagy számokra(pl. csak 0-15, vagy akár csak 0-7, de akár még a 0-255 is megfelel), akkor akár egy integerbe is tehetjük õket. Hogyan? Egyszerû. Csoportosítjuk a biteket. Tegyük fel, hogy maradván egy RP szervernél, el akarjuk tárolni ûbergigantikusan memóriakímélõ megoldással a játékos házának számát(ez legyen mondjuk 0-100 közötti szám), a játékos frakcióját(ez legyen 0-15 között), a játékos rangját(0-7 között), hogy a telefonja be van-e kapcsolva(0-1), a játékos szolgálati jármûvének id-jét(legyen 0-255 között), és az alapmunkáját(0-15 között). Ez is megoldható, egyetlen integerben, és ismételten 1000 slottal számolva máris megspóroltunk 4*4*1000+1*1000 byte-t(5 integer, és 1 boolean helyett csupán 1 integert használunk), ami ~16,6 kilobyte. Azért nem mindegy az, hogy 20,5 kb-ot vagy 4 kb-ot használunk szerintem... Ezt a következõ módszerrel valósíthatnánk meg:#define DATA_HOUSE 0#define DATA_FACTION 1#define DATA_RANK 2#define DATA_PHONESTATE 3#define DATA_DUTYCAR 4#define DATA_JOB 5stock GetData(playerinfo, data){ switch(data) { case DATA_HOUSE: return playerinfo & 127;//7 bit case DATA_FACTION: return playerinfo >> 7 & 15;//4 bit case DATA_RANK: return playerinfo >> 11 & 7;//3 bit case DATA_PHONESTATE: return playerinfo >> 14 & 1;//1 bit case DATA_DUTYCAR: return playerinfo >> 15 & 255;//8 bit case DATA_JOB: return playerinfo >> 23 & 15;//4 bit}return -1;}stock SetData(&playerinfo, data, value){ switch(data) { case DATA_HOUSE: { playerinfo = playerinfo & ~127 | value; }//7 bit (0-127 közötti maximálisan) case DATA_FACTION: { playerinfo = playerinfo & ~(15 << 7) | value << 7; }//4 bit (0-15 között) case DATA_RANK: { playerinfo = playerinfo & ~(7 << 11) | value << 11; }//3 bit (0-7 között) case DATA_PHONESTATE: { playerinfo = playerinfo & ~(1 << 14) | value << 14; }//1 bit (0-1) case DATA_DUTYCAR: { playerinfo = playerinfo & ~(255 << 15) | value << 15; }//8 bit (0-255 között) case DATA_JOB: { playerinfo = playerinfo & ~(15 << 23) | value << 23; }//4 bit (0-15 között) } return -1;}stock SetDatas(&playerinfo, house, faction, rank, phonestate, dutycar, job){playerinfo |= house | (faction << 7) | (rank << 11) | (phonestate << 14) | (dutycar << 15) | (job << 23);} Ez már azért elég morbid, ezt inkább csak érdekességképp mutattam, de éppenséggel ez is használható lenne, csak ehhez már kell egy kis õrültség 2.7 Második fejezet végeNos, láthattuk hogyan is mûködik mindazon sok \"marhaság\", amit az elsõ fejezetben elméletként átvettünk. Láthattuk hogyan mûködnek a jármûvek paneljének adataival babráló encode függvények, valamint megmutattam, hogyan írhatunk hozzájuk decode verziókat. Láthattunk még példát egyéb gyakorlati hasznosításra, kicsit megkoccoltuk a hexadecimális számok világát is, majd szóba került egy kis memóriatakarékosság. Végül mutattam egy összetettebb, elvetemült példát is, a bitmûveletek hasznosítására.3. Összegzés, végszó Úgy gondolom, hogy elég részletesen körbe jártuk a témát. Megnéztük a bitmûveletek elméleti, és gyakorlati oldalát is, láthattunk gyakorlati példákat. Nem kell elsõre megérteni ezt az egészet, sõt... Elegendõ ha egyszer elolvassuk, emésztjük kicsit, majd késõbb megint elõvesszük, akkor netán már kicsit többet is megértünk belõle. Nem egy kihagyhatatlan része a Pawno-nak, de azért van annak haszna, ha az ember jártas ebben a témában is, bár sokak szerint nem éri meg a sok fáradtságot, és idõt, mely a megértéséhez, begyakorlásához, használatának elsajátításához szükséges. Próbáltam mindent részletesen kifejteni. A bonyolultabb dolgokhoz sokat magyarázni, az egyszerûbbek esetén csak visszautalni az alapokhoz. Sok dolgot talán már túl is magyaráztam. Ezt a leírást nem 1 nap alatt készítettem el, kb. 1 hónapja írtam meg az elméleti oldalát, most az elmúlt pár napban pedig a gyakorlatot. Esténként 1-2 órát szántam rá csupán, és mivel ilyenkor már nem vagyok épp a toppon, elképzelhetõ, hogy néhány helyen helyesírási, vagy elvi hibát vétettem. Amennyiben valaki ilyet találna, kérem szóljon, és korrigálni fogom. Nos, azt hiszem nincs is más hátra, mint hogy sok szerencsét kívánjak a bitmûveletek elsajátításához, és megosszam a forrásokat. Remélem néhányan hasznosnak találják, eme igen hosszúra sikerült leírást.4. Források: Internet:WikipédiaSA:MP WikiSA:MP hivatalos fórumKönyv, e-book:Pohl László - A programozás alapjai(BME - Elektronikus Eszközök Tanszéke, Budapest, 2010)Siroki László - C programozás - Bitmûveletek jegyzeteˆ Anthony, 2012 - Minden jog fenntartva.A leírás egészben, vagy részleteiben szabadon terjeszthetõ, ameddig egyértelmûen fel van tüntetve az eredeti készítõ, valamint hogy honnan is származik. « Utoljára szerkesztve: 2012. augusztus 01. - 10:53:54 írta Anthony » Naplózva
00000000000000000000000000001000//8>>>200000000000000000000000000000010//2[/quote]Eddig nem igazán tapasztalhattunk semmi változást, minden pont ugyanúgy zajlott, mint a jobbra shiftelésnél(>>).Nézzük mi történik negatív szám esetén: 11111111111111111111111111111000//-8>>>200111111111111111111111111111110 //1073741822[/quote]Itt már láthatjuk a különbséget. Logikai shiftelés esetén az üres bitek helyére bekerülõ érték nem függ az MSB-tõl, hanem mindig 0. Emiatt soha nem kaphatunk eredményül negatív számot, logikai shiftelés esetén.1.5 Elsõ fejezet végeNos, ezennel átvettünk mindent, amit érdemes tudni a bitmûveletekrõl. Megmutattam az alapok, hogy hogyan épülnek fel a változók, valamint átvettük a fõ operátorokat. Eddig csak színtiszta elmélet volt. A következõ részben megmutatom, hogyan hasznosíthatjuk mindezt a gyakorlatban, vagyis mire is jó az a sok marhaság, amit eddig leírtam.2. Gyakorlati példák, érdekességek 2.1 Áttekintés, mikrõl is lesz még szóAz alábbiakban még pár dolgot szeretnék mutatni. Elõször is néhány \"függvénnyel\" fogok foglalkozni, majd mutatok még egy-két érdekességet. Konkrétan megmutatom, hogyan tudjuk egy jármû minden \"paneljének\" adatát kiíratni, részletesen(erre alapból nincs lehetõség, és a függvények is mind \"encode\" függvények, én viszont mutatok egy-egy decode \"verziót\" is.)Az alábbi 4 függvénnyel fogok foglalkozni: encode_tiresencode_panelsencode_doorsencode_lightsDe elõbb egy kis háttérinfó:A Pawnban a forrásban megadhatunk bináris számokat ugyanúgy, ahogyan Hex számokat is megadhatunk. Ehhez a \"0b\" prefixet kell használnunk. Tehát ha bináris számunk az 1011 és ezt akarjuk adni értékül egy változónak, azt így tehetjük meg: new var = 0b1011; 2.2 encode_tiresHáttér információ:A SA:MP-ban egy jármû abroncsainak adataid 4 bit tárolja. Az elsõ bit a jobb hátsó, a második bit a jobb elsõ, a harmadik bit a bal hátsó, a negyedik bit pedig a bal elsõ abroncsról tárol információt. Mivel 4 bitben eltárolhatóak az információk, így decimális formában 0-15 értékekkel is be lehet állítani a jármûvek abroncsait. 16-tól kezdve az értékek már ismétlõdni kezdenek, szóval a 16-nál az eredmény ugyanaz mint 0-nál, 17-nél ugyanaz mind 1-nél, és így tovább. Ez érthetõ, ha tudjuk hogyan épül fel a kettes számrendszer.(Ne felejtsük el, a biteket hátulról számozzuk!)Nos nézzük hogyan néz ki a kód: encode_tires(tire1, tire2, tire3, tire4){ return tire1 | tire2 << 1 | tire3 << 2 | tire4 << 3;} Láthatjuk, hogy bekérjük a 4 abroncs adatát(vagyis hogy ép[0], vagy kidurrant[1]), majd egy sima bitenkénti VAGY mûvelet és biteltolás kombinálásával visszatérünk az eredménnyel. A kódban a tire1 felel meg a jobb hátsó abroncsnak, a tire2 a jobb elsõ, a tire3 a bal hátsó, és a tire4 a bal elsõnek. Nézzünk egy konkrét példát. Tegyük fel, hogy én a hátsó 2 abroncsot ki akarom durrantani, még az elsõ kettõt épre akarom beállítani. Ez esetben így néz ki a dolog: encode_tire(1,0,1,0); Ha kiíratnánk konzolba a visszatérési értéket (%d-vel), 5-öt kapnánk. Miért is? Az 5 bináris alakja 0101. Hoppá, ha megnézzük ez pont fordítottja annak, amit mi megadtunk számjegyekként, csak már konkrét bináris számként. Nézzük hogyan is történhetett mindez, mit is csináltunk. A függvény a következõ mûveletet végzi: tire1 | tire2 << 1 | tire3 << 2 | tire4 << 3 Konyhanyelven úgy magyarázhatnánk el a dolgot, hogy ez a kód egymás mellé pakolgatja a megadott számokat, fordított sorrendben. Na de mi most nem a konyhanyelv miatt vagyunk ebben a leírásban, szóval nézzük a kódunkat. Lényegében ez futott le, a konkrét értékeinkkel nézve: 1 | 0 << 1 | 1 << 2 | 0 << 3 Bármily furcsa is, ez egy mûvelet, melyen szépen végig halad sorban a program. Elõször is írjuk át az egészet bináris alakba(az egyszerûség kedvéért most csak 4 bitet fogok kiírni): 0001 | 0000 << 1 | 0001 << 2 | 0000 << 3 Na erre se mondtuk volna rá a tutorial elsõ részének elolvasása elõtt, hogy ez egy mûveletsor. A rendszer elsõnek az eltolásokat végzi el (olyan ez mint a 2*3+2*4+2*5 mûveletben elõször a szorzásokat végezzük el, és csak utána adunk össze, ha nincs zárójel), és csak utána a bitenkénti VAGY mûveleteket, ezért mi is így fogunk tenni. Emlékezzünk, eltoláskor az \"üres\" bitek 0 értéket fognak felvenni. Végezzük el külön csak az eltolásokat: 0000 << 1 = 00000001 << 2 = 01000000 << 3 0000 Nos, ha ezeket elvégeztük, akkor nézzük, hogy is áll most a kódunk: 0001 | 0000 | 0100 | 0000 Négy azonos mûvelet van hátra, így hát a program innen más szépen sorban fog haladni. Tegyünk mi is így. Elsõ tehát a 0001 | 0000 mûvelet elvégzése. Emlékeztetõül, a bitenkénti VAGY mûvelet akkor ad 0-t eredményül, ha mind a kettõ érték 0, minden más esetben 1-et. Végezzük el külön az elsõ bitenkénti VAGY mûveletet: 0001 | 0000 = 0001 Nem volt bonyolult, nézzük tehát hogy áll akkor most a teljes mûveletünk: 0001 | 0100 | 0000 Haladunk szépen tovább, elvégezzük a 0001 | 0100 mûveletet. 0001 | 0100 = 0101 Nincs mit magyarázni rajta, egyszerû bitenkénti VAGY mûvelet volt ez is. Már csak ez van hátra: 0101 | 0000 Nos, ez sem lesz egy bonyolult mûvelet: 0101 | 0000 = 0101 Ezzel meg is kaptuk a végeredményt, ami nem más, mint a 0101 (tízes számrendszerben 5). Mivel azonban eléggé kusza lett így a számításunk, nézzük egyben hogyan is néz ki az egész, ha nem bontom ennyire részekre: 0100 | 0000 << 1 | 0001 << 2 | 0000 << 3v0001 | 0000 | 0100 | 0000v0001 | 0100 | 0000v0101 | 0000v0101 Nos, ezennel láthattuk a gyakorlatban is, hogyan mûködik a függvény. Kijelenthetjük tehát, hogy a függvény a számokat a sorrendjüknek megfelelõ számú bitbe helyezi. Mivel azonban a biteket hátulról számozzuk, így az 1,0,1,0 bemenetek végeredménye 0101 lesz. Így már nem is olyan bonyolult Na, ha már ilyen sokáig eljutottunk, akkor felhasználhatjuk ezt a kódot arra, hogy az alapjait figyelembe véve elkészítsük a decode változatot. Mivel 4 értékkel térünk vissza, ezért paraméterként kell majd 4 változó, melyeknek módosíthatjuk az értékét, illetve 1 paraméter pedig a dekódolandó szám. A függvényünk \"címsora\" tehát így néz ki: decode_tires(tires, &tire1, &tire2, &tire3, &tire4) Hogy példánk ne csak elmélet legyen, a tires változó legyen az elõbbi 0101 értékkel egyenlõ. Na akkor menjünk tovább. Bemeneti értékünk ugye a \"tires\" változóban lesz eltárolva, így ezzel fogjuk a mûveleteket végezni. Kezdjünk el gondolkozni. Hogyan is tudnánk ellenõrizni egy adatot. Legegyszerûbb ha a bitenkénti ÉS mûveletet alkalmazzuk. Miért is? Nos, a bitenkénti ÉS mûvelet ugye akkor ad eredményül 1-et, ha mind a 2 érték egy. Tehát ha min egy számot összehasonlítunk a 1-el(binárisan ugye 00000000000000000000000000000001, akkor az eredményünk lényegében arról fog tájékoztatni, hogy az elsõ bit 0, vagy 1. Nem is olyan bonyolult ez. Nézzük akkor az elsõ bitet. Arra emlékezhetünk még, hogy az elsõ bit a tire1, vagyis jobb hátsó abroncs adatait tárolja. Tehát: tire1 = tires & 1; Vagy konkrétan a példánk esetében: tire1 = 0101 & 0001 Mivel a bitenkénti ÉS mûvelet akkor ad 1-et eredményül, ha mind a kettõ bit 1, akkor már láthatjuk is, hogy miért elegendõ az elsõ bitet szemügyre venni. Mivel mind a 2 helyen az elsõ bit 1, ezért a tire1 változónk értéke is 1(0001) lesz. Egyszerû és nagyszerû, máris megkaptuk a jobb hátsó abroncs állapotát.Gondolkozzunk hát tovább. Nekünk most arra van szükségünk, hogy a 2. bit értékét megkapjuk. Hogyan kaphatjuk ezt meg? Legegyszerûbb megoldás, ha a tires változót jobbra shifteljük 1-el, majd ismételten bitenkénti ÉS mûveletet végzünk. Nézzük tehát, hogyan is nézne ez ki: tire2= tires >> 1 & 1; Vagy konkrétan a mi példánkban: tire2 = 0101 >> 1 & 0001vtire2 = 0010 & 0001 Ismételten bitenkénti ÉS mûvelet, és az eredményünk 0, mivel csak az egyik bit 1, a másik 0, így a bitenkénti ÉS mûvelet eredménye is 0. Ezen az elven megcsináljuk a maradék 2 abronccsal is: tire3= tires >> 2 & 1;tire4= tires >> 3 & 1; Vagy a mi esetünkben konkrét példánkkal: tire3 = 0101 >> 2 & 0001 // azaz: --> 0001 & 0001 = 1tire4 = 0101 >> 3 & 0001 // azaz: --> 0000 & 0001 = 0 A teljes kódunk tehát: decode_tires(tires, &tire1, &tire2, &tire3, &tire4){ tire1 = tires & 1; tire2 = tires >> 1 & 1; tire3 = tires >> 2 & 1; tire4 = tires >> 3 & 1;} A konkrét példánkban a 4 változónk értéke a függvény lefutása után: tire1: 1tire2: 0tire3: 1tire4: 0 Ha összehasonlítjuk, ezek pont azok az értékek, mint amiket az encode függvényben megadtunk, tehát úgy tûnik jól dolgoztunk 2.3 encode_lightsEz a rész lesz a legrövidebb. Az egész lámpás rész pontosan ugyanarra a sémára épül mint az abroncsok. Az adott bitek ugyanazon a \"helyen\" lévõ lámpa adatait szolgáltatják, ugyanúgy 0 az ép, és 1 a sérült lámpák állapot azonosítója, és ugyanúgy 1-1 bit tárolja az értékeket.Maga a kód: encode_lights(light1, light2, light3, light4){ return light1 | light2 << 1 | light3 << 2 | light4 << 3;} Láthatjuk hogy halál pontosan ugyanaz mint az encode_tires, csak más változónevekkel. Így hát a decode függvény is ugyan az lesz, csak a változóneveket kell módosítanunk: decode_lights(lights, &light1, &light2, &light3, &light4){ light1 = lights & 1; light2 = lights >> 1 & 1; light3 = lights >> 2 & 1; light4 = lights >> 3 & 1;} Ha megnézzük látjuk, hogy teljesen ugyan az a séma, ugyan az a felépítés, minden megegyezik, ezért nem is írtam le újból ugyanazt, amit egyszer már kifejtettem.2.4 encode_panelsHáttér információ:A SA:MP 7 panelrõl tárol adatokat. Egy panel adatait 4 bit írja le. Nos, ennél több infóra nekünk most nincs szükségünk, az elõzõ kód alapján ezen kód is megérthetõ, így az encode verzióhoz nem is nagyon szeretnék magyarázatot fûzni. A különbség az encode_tires-hez képest, hogy itt 4-essével shiftelünk, mivel 1 panel adatait 4 bit tárolja.Maga a kód: encode_panels(flp, frp, rlp, rrp, windshield, front_bumper, rear_bumper){ return flp | frp << 4 | rlp << 8 | rrp << 12 | windshield << 16 | front_bumper << 20 | rear_bumper << 24;} Nézzünk egy decode verziót, ehhez a kódhoz. Alapjaiban ugyanazon a vonalon kell elindulnunk, mint a decode_tires esetében. Itt most 7 visszatérési értékünk lesz, és 1 bemeneti érték. Tehát így néz ki a függvény header: decode_panels(panels, &flp, &frp, &rlp, &rrp, &windshield, &front_bumper, &rear_bumper) A folytatásban megint csak az elõbbi verziót kell alapul vennünk, azonban itt lesz egy kis különbség. Egyértelmû, hogy mivel 1 panel adatait 4 bit tárolja, így a 4 bitet nekünk egyben kell lekérni, és egyben kell vele visszatérnünk. Megint csak a bitenkénti ÉS mûveletet fogjuk alkalmazni, azonban mivel 4 bitrõl van szó, így egy olyan számmal kell összehasonlítanunk, mely bináris alakban 1111. Ez a szám nem más, mint a 15. Hogy miért ez kell?Ha egy bitsorozat tartalmát ki szeretnénk másolni egy másik változóba, akkor a bitenkénti ÉS mûveletre vna szükségünk. A bitenkénti ÉS mûvelet akkor ad 1-et eredményül, ha mindkét bit 1, ha valamelyik 0, akkor a visszatérés már 0 lesz. Ergó tehát, ha egy csupa 1-esekbõl álló bitsorozattal vetjük össze az eredeti bitsorozatunkat, akkor lényegében lemásoljuk a sorozatot, egy másik változóba, hisz eredményül mindig az eredeti sozatot fogjuk visszakapni. Íme néhány példa: 0101 & 1111 = 01011111 & 1111 = 11110000 & 1111 = 00001011 & 1111 = 1011 Láthatjuk tehát, hogy ha csupa 1-est tartalmazó bitsorozattal hasonlítjuk össze az eredeti számunkat, akkor lényegében egy másolatot készítünk. Mivel nekünk erre a 4 bitre van szükségünk a 32-bõl, így pontosan ezt kell tennünk, ezen 4 bitet lemásolni egy másik változóba, hogy késõbb ezt majd egy másik függvénnyel még jobban darabokra szedhessük.Íme tehát akkor a kódunk elsõ sora: flp = panels & 15; Mivel 4-esével vizsgáljuk a biteket, így lényegében annyit kell még változtatnunk az elõzõ kódon(decode_tires), hogy az 1-esével növekvõ shiftelések helyett 4-esével shifteljünk. Azaz a 2. panel adatainak lekérdezése: frp = panels >> 4 & 15; Innen pedig már csak mindig további 4 shiftelést el kell végeznünk, és szépen végig haladhatunk a panels változó bitjein, és megkaphatjuk minden panel adatait külön-külön. rlp = panels >> 8 & 15;rrp = panels >> 12 & 15;windshield = panels >> 16 & 15;front_bumper= panels >> 20 & 15;rear_bumper = panels >> 24 & 15; Ezennel minden panel adatait megkaptuk. Ezután már akár a decode_tires függvényt felhasználva szét lehet szedni az adatokat darabokra, de akár egy saját névvel, és más változó nevekkel ellátott másik függvényt is létre lehet hozni a decode_tires mintájára.Tehát az egész kód egyben: decode_panels(panels, &flp, &frp, &rlp, &rrp, &windshield, &front_bumper, &rear_bumper){ flp = panels & 15; frp = panels >> 4 & 15; rlp = panels >> 8 & 15; rrp = panels >> 12 & 15; windshield = panels >> 16 & 15; front_bumper= panels >> 20 & 15; rear_bumper = panels >> 24 & 15;} Igazából a függvény ugyanarra a sémára épül mint a decode_tires, csak annyi a különbség, hogy 4 bitenként kapjuk az adatokat, nem pedig egyesével, mivel itt minden panelnek 4 adatát tudjuk majd még lekérdezni. Nem tudom lehet-e még ehhez a részhez hozzáfûzni valamit, szóval szerintem ugorjunk. 2.5 encode_doorsHáttér információ:A SA:MP képes minden jármû 4 \"ajtajának\" állapotát tárolni. Ezek az \"ajtók\"(Doors-t hogy fordíthatnám jobban?) konkrétan a motorháztetõ, csomagtartó, vezetõoldali ajtó, és anyósülés oldali ajtó. A 2 hátsó ajtó állapotát jelenleg még nincs lehetõségünk lekérdezni.Amikor lekérdezzük egy jármû \"ajtajának\" adatait, azt a SA:MP 4 bájtként(32 bit) adja vissza. Ebbõl a 4 bájtból 1 bájt(8 bit) jut minden egyes objektumra. Vagyis minden egyes objektum adatait 8 bit tárolja. Vagyis tárolhatná...A SA:MP csak az elsõ 3 bitet használja, a további bitek üresek lesznek, nincs funkciójuk, talán majd késõbb lesz. Az elsõ bit azt tárolja, hogy az objektum zárt[0], vagy nyitott[1](nem az jelenti, hogy bezárt, hanem becsukott/kinyitott) állapotban van-e. A második bit azt adja meg, hogy az objektum ép[0] vagy sérült[1]. A 3. bit arról tájékoztat, hogy az adott objektum rajta van-e még a jármûvön[0] vagy már leesett[1]. A többi bit mint mondtam üres. Ez majd a decode függvénynél lesz fontos.Nézzük tehát a konkrét kódot: encode_doors(bonnet, boot, driver_door, passenger_door, behind_driver_door, behind_passenger_door){ #pragma unused behind_driver_door #pragma unused behind_passenger_door return bonnet | boot << 8 | driver_door << 16 | passenger_door << 24;} A 2 hátsó ajtóval nem foglalkozunk, mivel azok jelenleg még nem támogatottak ugyebár, de a kód készítõje gondolt a jövõre is, vagy fogalmam sincs miért adta meg ezt a két paramétert is, hisz ígyis-úgyis módosítani kell a kódon... Ha behoznak még 2 ajtót, akkor az 1 bájtos tárolással már 6 bájt lenne, de 1 változó maximum 4 bájt lehet, tehát a kódot úgyis módosítani kell, ha meg arra gondoltak, hogy legalább a paramétereket nem kell majd szegény scriptereknek minden sorban átírni, az megint hülyeség, hisz ki adna értéket olyan paraméternek, amely nincs használva?...Nos mindegy, nem a kritizálásért vagyunk most itt. Ha megnézzük, a kód 8-as shiftelést használ, mivel egy objektum adatait 8 bit tárolja. A mûködési elv ugyanaz, mint az elõzõ encode függvényeknél, így erre nem kívánok megint kitérni.Nézzük azonban hogyan írnánk ehhez egy decode függvényt. Mikor megkérdeztem errõl ismerõsömet, õ azt mondta, ugyanúgy mint az elõzõ, decode_panels függvénynél, csak 8-as shiftelést használunk, és 15 helyett 255-el végezzük a bitenkénti ÉS mûveletet. 255-el? minek?No erre is ki fogok térni késõbb, de haladjunk a szokásos módon. 1 bemeneti paraméterünk van, és 4 kimeneti, vagyis a header rész a következõ: decode_doors(doors, &bonnet, &boot, &driver_door, &passenger_door) Ezzel meg is vagyunk, itt nem látok különösebb magyarázni valót. Én most nem fogok törõdni a 2 nem létezõ ajtóval, ha egy jövõbeni verzióban netán azokat is behoznák mint lekérdezhetõ objektum, akkor ígyis-úgyis módosítani kell majd a kódot, addig meg úgysincs rá szükségünk.Nos, mint az eddigi decode függvényeknél, elsõ feladatunk egy shiftelés mentes bitenkénti ÉS mûvelet elvégzése. Az egyik megoldás a következõ: bonnet = doors & 255; Lehet így is. Azonban véleményem szerint felesleges itt egy ekkora számmal \"szenvednünk\". Miért használjunk ekkora számot, és ezáltal bonyolítsuk feleslegesen a kódunkat, ha nekünk a 8 bitbõl csak az elsõ 3 lesz ami adatot tárol? Bõven elegendõ egy olyan szám, amely bináris alakja 111. Ez nem más, mint a 7. Tehát a következõ megoldás ugyanúgy helyes: bonnet = doors & 7; A két kód pontosan ugyanazt az eredményt fogja visszaadni, semmi különbség a kettõ között. Csupán elsõ esetében nagy számmal dolgozunk, még második verzió esetén csak akkorával, amekkora szükséges.A kód többi részében csak 8-as shifteléssel végigcammogunk a doors változó többi bitjén is: boot = doors >> 8 & 7;driver_door = doors >> 16 & 7;passenger_door = doors >> 24 & 7; A teljes kód egyben: decode_doors(doors, &bonnet, &boot, &driver_door, &passenger_door){ bonnet = doors & 7; boot = doors >> 8 & 7; driver_door = doors >> 16 & 7; passenger_door = doors >> 24 & 7;} A kód pont ugyanazon az elven épül fel, mint az összes többi decode függvényünk, csak épp a benne használatos értékek az \"ajtó\" objectumok adatstruktúrájához vannak igazítva.2.6 Egyéb ötletek gyakorlati alkalmazásra, hasznosításraJoggal felmerülhet a kérdés, hogy ennyi? Emiatt rágtuk végig magunkat ezen néhány tízezer karakteren? Nem. Nem ennyi. A SA:MP-ban rengeteg felhasználási módja lehet még a bitmûveleteknek. Mutatok is néhány dolgot, mire jók még.2.6.1 RGBA->HEX; HEX->RGBA konvertálásokNos, bármily \"hihetetlen\", erre is használható. Miért is? Nos, 16-os számrendszerben 0-9, valamint A-F \"szánjegyek\" léteznek. A hexadecimális számjegy decimális értékben 0-15 lehet. Ha megnézzük bináris értékeket, 0-15 az 4 bittel leírható, hiszen a 15 az bináris alakban 1111. De ha józan paraszti ésszel belegondolunk is. 16 az 24. Így tehát 1 hexadecimális érték, az 4 bináris értékkel írható le. Ebbõl már sejthetõ, hogy itt is shiftelésre lesz szükségünk. Naná Mivel 4 bit ír le egy hexadecimális számjegyet, így 4-esével kell majd shiftelnünk.Mi is az az RGBA?RGBA: A színek olyan meghatározási módja, melyben a 3 alapszín([r]ed, [g]reen, lue) mellett egy 4. érték, az úgynevezett [a]lpha is szerepel, amely az áttetszõséget adja meg. A legtöbb program(pl. paint, PS), és pl. a css(külön függvénnyel!) is elfogadja ezen értékeket decimális számokként(0-255) is, a SA:MP-ban azonban hexadecimális alakban kell megadnunk. Bármily furcsa a következõ 4 sor pont ugyan azt jelenti:\"Normál\" RGB alak(pl. paint, photoshop): Piros: 0, Zöld: 255, Kék: 255CSS alak(függvénnyel): rgba(0, 255, 255, 1);Pawn alak(1): 0x00FFFFFFPawn alak(2): 16777215 Ó igen, a Pawn-ban decimális számokként is megadhatjuk a színeket, hisz a program mindent bináris számrendszerbe fog átszámolni. De akár bináris alakban is megadhattuk volna:Pawn alak(3): 0b111111111111111111111111 Az, hogy mi a Pawn-nak hexadecimális alakban szoktuk megadni, annak az egyetlen oka, hogy így a leginkább \"átlátható\", és ez egy amúgy is elfogadott módja (mármint a hexadecimális számmal való megadás) a színek leírásának a számítástechnikában.Nos, mivel 1 szín 0 és 255 közötti értéket tartalmaz a piros, zöld és kék színekbõl, ez hexadecimálisan 2 számjeggyel leírható: 00-FF. Az FF értéke 255, aki nem hiszi, számoljon utána (162-1)2 hexadecimális számjegy az pedig 8 bináris számjeggyel leírható(00000000-11111111). Így már láthatjuk, hogy 8-as shiftelésre lesz szükségünk. Másra nincs is szükségünk, az RGBA->HEX konverter elkészítéséhez.stock RGBAToHex( r, g, b, a ){return a | b << 8 | g << 16 | r << 24;} Az, hogy miért fordított sorrendben kellett megadnunk az értékeket, az megint csak azzal magyarázható, hogy a biteket hétköznapi értelemben véve visszafelé számozzuk.Egy ellentét, azaz HexToRGBA függvény megírása sem túl bonyolult, ugyan ezt kell csinálnunk, csak visszafelé.stock HexToRGBA( colour, &r, &g, &b, &a ){a = colour & 0xFF;b = (colour >> 8 ) & 0xFF;g = (colour >> 16 ) & 0xFF;r = (colour >> 24 ) & 0xFF;} A 0xFF-et természetesen decimális értékkel is megadhatjuk(255).No akkor lehet ugye RGBA-t ARGB-be is konvertálni, az így néz ki:RGBAToARGBHEX(r,g,b,a){return (r & 0xff) << 24 | (g & 0xff) << 16 | (b & 0xff) << 8 | a & 0xff;} Ez máris egy újabb felhasználási módja a bitmûveleteknek. De, mutatok még 2.6.2 Boolen változók helyett IntegerAz al-al-alcímet elolvasva joggal felmerülhet a kérdés, hogy ez meg hogy jön ide. Nos ez úgy jön ide, hogy most egy kis memóriatakarékosságról fogok itt regélni. Adhattam volna azt is címnek, hogy miért ne használjunk boolean változókat, ha legalább 5 igaz/hamis értéket el akarunk tárolni.Nos akkor egy kis memórialecke. Azt már tisztáztuk, hogy az integer változók 32 bitesek, vagyis 4 bájtot foglalnak le a memóriában maguknak. A boolean típusú változókról azonban még nem volt szó. Mivel 1 értéket tárolnak csak, az is csak igaz vagy hamis lehet, így joggal gondolhatnánk, hogy 1 bit a mérete. Azonban nem...1 boolean változó 8 bitet, azaz 1 bájtot foglal le magának a memóriában. Hogy ez miért így van, az a számítógépek memóriahasználatával magyarázható. Manapság nincs mód arra, hogy a memóriában egyetlen bitet módosítsunk, csak 8-as \"csomagokban\", vagyis bájtonként tudjuk ezt megtenni. Hogy ez miért van így, azt most nem fogom itt fejtegetni Nos, ha megnézzük tehát, a boolean változó egy nagyon-nagyon pazarló megoldás. Hiszen 1 darab boolean változó 1 bájtot foglal, 4 darab boolean változó esetén ez már 4 bájt, ami megegyezik 1 integer változó méretével. Azonban, még a 4 boolean változóban 4 igaz/hamis érték kerülhet tárolásra, addig egy integerben 32 igaz/hamis értéket is eltárolhatunk.Bizony, 32 igaz/hamis érték. Hiszen 1 integer változó 32 bitbõl áll, ha minden bitet hasznosítunk, akkor a bitmûveletek felhasználásával 32 igaz/hamis értéket is eltárolhatunk. Összehasonlítva:32 darab boolean változó mérete 32 bájt.1 integer változó mérete: 4 bájt. Vagyis 32 boolean változó esetén nyolcad annyi memóriánkba kerül, ha egy darab integerben tároljuk el az értékeket. Persze 32 booleanra ritkán van szükség, de egy RP modban simán elõfordulhat. Gondoljunk csak bele... Van 32 boolean-tömbös változónk, ez a maximum slotszámot(1000) figyelembe véve: 32*1000 byte azaz 32000 byte ami ~31 kilobyte. Ha ezt egyetlen integer-tömbbel oldanánk meg: 4*1000 byte, azaz 4000 byte, ami ~3,9 kilobyte. Azért így nézve már nem annyira \"elhanyagolandó\" az a különbség szerintem =)Akár írhatnánk erre külön egy függvényt, mely az adott bit értékével tér vissza. Nem is bonyolult. Így már azok számára is használhatóak a bitmûveletek, akik nem értenek hozzá, és rengeteg memóriát takaríthatnak meg.Íme egy függvény az adatok kiolvasására:stock GetData(integer, bit){ return integer >> bit & 1; //vagy az 1 helyett akár 0x01 alakban is megadhatjuk, nincs jelentõsége} És a párja, adat írásra:stock SetData(&integer, bit, value){ integer |= value << bit; return 1;} Persze ezt még lehetne csinosítani, hogy pl. ne 0-tól hanem 1-tõl számoljuk a biteket, vagy hogy 31(vagy 32 ha 1-tõl számolunk) felett 0 legyen a return értéke, esetleg a value-re is rakhatnánk ellenõrzést, hogy 0-1-e az értéke, vagy más, de ilyenekkel most nem törõdtem.2.6.3 Több érték, egy integerbeNa ez az a rész, ami véleményem szerint már csak a nagyon megrögzött memóriafelszabadítók szerint nem lesz felesleges Amennyiben nincs szükségünk túl nagy számokra(pl. csak 0-15, vagy akár csak 0-7, de akár még a 0-255 is megfelel), akkor akár egy integerbe is tehetjük õket. Hogyan? Egyszerû. Csoportosítjuk a biteket. Tegyük fel, hogy maradván egy RP szervernél, el akarjuk tárolni ûbergigantikusan memóriakímélõ megoldással a játékos házának számát(ez legyen mondjuk 0-100 közötti szám), a játékos frakcióját(ez legyen 0-15 között), a játékos rangját(0-7 között), hogy a telefonja be van-e kapcsolva(0-1), a játékos szolgálati jármûvének id-jét(legyen 0-255 között), és az alapmunkáját(0-15 között). Ez is megoldható, egyetlen integerben, és ismételten 1000 slottal számolva máris megspóroltunk 4*4*1000+1*1000 byte-t(5 integer, és 1 boolean helyett csupán 1 integert használunk), ami ~16,6 kilobyte. Azért nem mindegy az, hogy 20,5 kb-ot vagy 4 kb-ot használunk szerintem... Ezt a következõ módszerrel valósíthatnánk meg:#define DATA_HOUSE 0#define DATA_FACTION 1#define DATA_RANK 2#define DATA_PHONESTATE 3#define DATA_DUTYCAR 4#define DATA_JOB 5stock GetData(playerinfo, data){ switch(data) { case DATA_HOUSE: return playerinfo & 127;//7 bit case DATA_FACTION: return playerinfo >> 7 & 15;//4 bit case DATA_RANK: return playerinfo >> 11 & 7;//3 bit case DATA_PHONESTATE: return playerinfo >> 14 & 1;//1 bit case DATA_DUTYCAR: return playerinfo >> 15 & 255;//8 bit case DATA_JOB: return playerinfo >> 23 & 15;//4 bit}return -1;}stock SetData(&playerinfo, data, value){ switch(data) { case DATA_HOUSE: { playerinfo = playerinfo & ~127 | value; }//7 bit (0-127 közötti maximálisan) case DATA_FACTION: { playerinfo = playerinfo & ~(15 << 7) | value << 7; }//4 bit (0-15 között) case DATA_RANK: { playerinfo = playerinfo & ~(7 << 11) | value << 11; }//3 bit (0-7 között) case DATA_PHONESTATE: { playerinfo = playerinfo & ~(1 << 14) | value << 14; }//1 bit (0-1) case DATA_DUTYCAR: { playerinfo = playerinfo & ~(255 << 15) | value << 15; }//8 bit (0-255 között) case DATA_JOB: { playerinfo = playerinfo & ~(15 << 23) | value << 23; }//4 bit (0-15 között) } return -1;}stock SetDatas(&playerinfo, house, faction, rank, phonestate, dutycar, job){playerinfo |= house | (faction << 7) | (rank << 11) | (phonestate << 14) | (dutycar << 15) | (job << 23);} Ez már azért elég morbid, ezt inkább csak érdekességképp mutattam, de éppenséggel ez is használható lenne, csak ehhez már kell egy kis õrültség 2.7 Második fejezet végeNos, láthattuk hogyan is mûködik mindazon sok \"marhaság\", amit az elsõ fejezetben elméletként átvettünk. Láthattuk hogyan mûködnek a jármûvek paneljének adataival babráló encode függvények, valamint megmutattam, hogyan írhatunk hozzájuk decode verziókat. Láthattunk még példát egyéb gyakorlati hasznosításra, kicsit megkoccoltuk a hexadecimális számok világát is, majd szóba került egy kis memóriatakarékosság. Végül mutattam egy összetettebb, elvetemült példát is, a bitmûveletek hasznosítására.3. Összegzés, végszó Úgy gondolom, hogy elég részletesen körbe jártuk a témát. Megnéztük a bitmûveletek elméleti, és gyakorlati oldalát is, láthattunk gyakorlati példákat. Nem kell elsõre megérteni ezt az egészet, sõt... Elegendõ ha egyszer elolvassuk, emésztjük kicsit, majd késõbb megint elõvesszük, akkor netán már kicsit többet is megértünk belõle. Nem egy kihagyhatatlan része a Pawno-nak, de azért van annak haszna, ha az ember jártas ebben a témában is, bár sokak szerint nem éri meg a sok fáradtságot, és idõt, mely a megértéséhez, begyakorlásához, használatának elsajátításához szükséges. Próbáltam mindent részletesen kifejteni. A bonyolultabb dolgokhoz sokat magyarázni, az egyszerûbbek esetén csak visszautalni az alapokhoz. Sok dolgot talán már túl is magyaráztam. Ezt a leírást nem 1 nap alatt készítettem el, kb. 1 hónapja írtam meg az elméleti oldalát, most az elmúlt pár napban pedig a gyakorlatot. Esténként 1-2 órát szántam rá csupán, és mivel ilyenkor már nem vagyok épp a toppon, elképzelhetõ, hogy néhány helyen helyesírási, vagy elvi hibát vétettem. Amennyiben valaki ilyet találna, kérem szóljon, és korrigálni fogom. Nos, azt hiszem nincs is más hátra, mint hogy sok szerencsét kívánjak a bitmûveletek elsajátításához, és megosszam a forrásokat. Remélem néhányan hasznosnak találják, eme igen hosszúra sikerült leírást.4. Források: Internet:WikipédiaSA:MP WikiSA:MP hivatalos fórumKönyv, e-book:Pohl László - A programozás alapjai(BME - Elektronikus Eszközök Tanszéke, Budapest, 2010)Siroki László - C programozás - Bitmûveletek jegyzeteˆ Anthony, 2012 - Minden jog fenntartva.A leírás egészben, vagy részleteiben szabadon terjeszthetõ, ameddig egyértelmûen fel van tüntetve az eredeti készítõ, valamint hogy honnan is származik. « Utoljára szerkesztve: 2012. augusztus 01. - 10:53:54 írta Anthony » Naplózva
11111111111111111111111111111000//-8>>>200111111111111111111111111111110 //1073741822[/quote]Itt már láthatjuk a különbséget. Logikai shiftelés esetén az üres bitek helyére bekerülõ érték nem függ az MSB-tõl, hanem mindig 0. Emiatt soha nem kaphatunk eredményül negatív számot, logikai shiftelés esetén.1.5 Elsõ fejezet végeNos, ezennel átvettünk mindent, amit érdemes tudni a bitmûveletekrõl. Megmutattam az alapok, hogy hogyan épülnek fel a változók, valamint átvettük a fõ operátorokat. Eddig csak színtiszta elmélet volt. A következõ részben megmutatom, hogyan hasznosíthatjuk mindezt a gyakorlatban, vagyis mire is jó az a sok marhaság, amit eddig leírtam.2. Gyakorlati példák, érdekességek 2.1 Áttekintés, mikrõl is lesz még szóAz alábbiakban még pár dolgot szeretnék mutatni. Elõször is néhány \"függvénnyel\" fogok foglalkozni, majd mutatok még egy-két érdekességet. Konkrétan megmutatom, hogyan tudjuk egy jármû minden \"paneljének\" adatát kiíratni, részletesen(erre alapból nincs lehetõség, és a függvények is mind \"encode\" függvények, én viszont mutatok egy-egy decode \"verziót\" is.)Az alábbi 4 függvénnyel fogok foglalkozni: encode_tiresencode_panelsencode_doorsencode_lightsDe elõbb egy kis háttérinfó:A Pawnban a forrásban megadhatunk bináris számokat ugyanúgy, ahogyan Hex számokat is megadhatunk. Ehhez a \"0b\" prefixet kell használnunk. Tehát ha bináris számunk az 1011 és ezt akarjuk adni értékül egy változónak, azt így tehetjük meg: new var = 0b1011; 2.2 encode_tiresHáttér információ:A SA:MP-ban egy jármû abroncsainak adataid 4 bit tárolja. Az elsõ bit a jobb hátsó, a második bit a jobb elsõ, a harmadik bit a bal hátsó, a negyedik bit pedig a bal elsõ abroncsról tárol információt. Mivel 4 bitben eltárolhatóak az információk, így decimális formában 0-15 értékekkel is be lehet állítani a jármûvek abroncsait. 16-tól kezdve az értékek már ismétlõdni kezdenek, szóval a 16-nál az eredmény ugyanaz mint 0-nál, 17-nél ugyanaz mind 1-nél, és így tovább. Ez érthetõ, ha tudjuk hogyan épül fel a kettes számrendszer.(Ne felejtsük el, a biteket hátulról számozzuk!)Nos nézzük hogyan néz ki a kód: encode_tires(tire1, tire2, tire3, tire4){ return tire1 | tire2 << 1 | tire3 << 2 | tire4 << 3;} Láthatjuk, hogy bekérjük a 4 abroncs adatát(vagyis hogy ép[0], vagy kidurrant[1]), majd egy sima bitenkénti VAGY mûvelet és biteltolás kombinálásával visszatérünk az eredménnyel. A kódban a tire1 felel meg a jobb hátsó abroncsnak, a tire2 a jobb elsõ, a tire3 a bal hátsó, és a tire4 a bal elsõnek. Nézzünk egy konkrét példát. Tegyük fel, hogy én a hátsó 2 abroncsot ki akarom durrantani, még az elsõ kettõt épre akarom beállítani. Ez esetben így néz ki a dolog: encode_tire(1,0,1,0); Ha kiíratnánk konzolba a visszatérési értéket (%d-vel), 5-öt kapnánk. Miért is? Az 5 bináris alakja 0101. Hoppá, ha megnézzük ez pont fordítottja annak, amit mi megadtunk számjegyekként, csak már konkrét bináris számként. Nézzük hogyan is történhetett mindez, mit is csináltunk. A függvény a következõ mûveletet végzi: tire1 | tire2 << 1 | tire3 << 2 | tire4 << 3 Konyhanyelven úgy magyarázhatnánk el a dolgot, hogy ez a kód egymás mellé pakolgatja a megadott számokat, fordított sorrendben. Na de mi most nem a konyhanyelv miatt vagyunk ebben a leírásban, szóval nézzük a kódunkat. Lényegében ez futott le, a konkrét értékeinkkel nézve: 1 | 0 << 1 | 1 << 2 | 0 << 3 Bármily furcsa is, ez egy mûvelet, melyen szépen végig halad sorban a program. Elõször is írjuk át az egészet bináris alakba(az egyszerûség kedvéért most csak 4 bitet fogok kiírni): 0001 | 0000 << 1 | 0001 << 2 | 0000 << 3 Na erre se mondtuk volna rá a tutorial elsõ részének elolvasása elõtt, hogy ez egy mûveletsor. A rendszer elsõnek az eltolásokat végzi el (olyan ez mint a 2*3+2*4+2*5 mûveletben elõször a szorzásokat végezzük el, és csak utána adunk össze, ha nincs zárójel), és csak utána a bitenkénti VAGY mûveleteket, ezért mi is így fogunk tenni. Emlékezzünk, eltoláskor az \"üres\" bitek 0 értéket fognak felvenni. Végezzük el külön csak az eltolásokat: 0000 << 1 = 00000001 << 2 = 01000000 << 3 0000 Nos, ha ezeket elvégeztük, akkor nézzük, hogy is áll most a kódunk: 0001 | 0000 | 0100 | 0000 Négy azonos mûvelet van hátra, így hát a program innen más szépen sorban fog haladni. Tegyünk mi is így. Elsõ tehát a 0001 | 0000 mûvelet elvégzése. Emlékeztetõül, a bitenkénti VAGY mûvelet akkor ad 0-t eredményül, ha mind a kettõ érték 0, minden más esetben 1-et. Végezzük el külön az elsõ bitenkénti VAGY mûveletet: 0001 | 0000 = 0001 Nem volt bonyolult, nézzük tehát hogy áll akkor most a teljes mûveletünk: 0001 | 0100 | 0000 Haladunk szépen tovább, elvégezzük a 0001 | 0100 mûveletet. 0001 | 0100 = 0101 Nincs mit magyarázni rajta, egyszerû bitenkénti VAGY mûvelet volt ez is. Már csak ez van hátra: 0101 | 0000 Nos, ez sem lesz egy bonyolult mûvelet: 0101 | 0000 = 0101 Ezzel meg is kaptuk a végeredményt, ami nem más, mint a 0101 (tízes számrendszerben 5). Mivel azonban eléggé kusza lett így a számításunk, nézzük egyben hogyan is néz ki az egész, ha nem bontom ennyire részekre: 0100 | 0000 << 1 | 0001 << 2 | 0000 << 3v0001 | 0000 | 0100 | 0000v0001 | 0100 | 0000v0101 | 0000v0101 Nos, ezennel láthattuk a gyakorlatban is, hogyan mûködik a függvény. Kijelenthetjük tehát, hogy a függvény a számokat a sorrendjüknek megfelelõ számú bitbe helyezi. Mivel azonban a biteket hátulról számozzuk, így az 1,0,1,0 bemenetek végeredménye 0101 lesz. Így már nem is olyan bonyolult Na, ha már ilyen sokáig eljutottunk, akkor felhasználhatjuk ezt a kódot arra, hogy az alapjait figyelembe véve elkészítsük a decode változatot. Mivel 4 értékkel térünk vissza, ezért paraméterként kell majd 4 változó, melyeknek módosíthatjuk az értékét, illetve 1 paraméter pedig a dekódolandó szám. A függvényünk \"címsora\" tehát így néz ki: decode_tires(tires, &tire1, &tire2, &tire3, &tire4) Hogy példánk ne csak elmélet legyen, a tires változó legyen az elõbbi 0101 értékkel egyenlõ. Na akkor menjünk tovább. Bemeneti értékünk ugye a \"tires\" változóban lesz eltárolva, így ezzel fogjuk a mûveleteket végezni. Kezdjünk el gondolkozni. Hogyan is tudnánk ellenõrizni egy adatot. Legegyszerûbb ha a bitenkénti ÉS mûveletet alkalmazzuk. Miért is? Nos, a bitenkénti ÉS mûvelet ugye akkor ad eredményül 1-et, ha mind a 2 érték egy. Tehát ha min egy számot összehasonlítunk a 1-el(binárisan ugye 00000000000000000000000000000001, akkor az eredményünk lényegében arról fog tájékoztatni, hogy az elsõ bit 0, vagy 1. Nem is olyan bonyolult ez. Nézzük akkor az elsõ bitet. Arra emlékezhetünk még, hogy az elsõ bit a tire1, vagyis jobb hátsó abroncs adatait tárolja. Tehát: tire1 = tires & 1; Vagy konkrétan a példánk esetében: tire1 = 0101 & 0001 Mivel a bitenkénti ÉS mûvelet akkor ad 1-et eredményül, ha mind a kettõ bit 1, akkor már láthatjuk is, hogy miért elegendõ az elsõ bitet szemügyre venni. Mivel mind a 2 helyen az elsõ bit 1, ezért a tire1 változónk értéke is 1(0001) lesz. Egyszerû és nagyszerû, máris megkaptuk a jobb hátsó abroncs állapotát.Gondolkozzunk hát tovább. Nekünk most arra van szükségünk, hogy a 2. bit értékét megkapjuk. Hogyan kaphatjuk ezt meg? Legegyszerûbb megoldás, ha a tires változót jobbra shifteljük 1-el, majd ismételten bitenkénti ÉS mûveletet végzünk. Nézzük tehát, hogyan is nézne ez ki: tire2= tires >> 1 & 1; Vagy konkrétan a mi példánkban: tire2 = 0101 >> 1 & 0001vtire2 = 0010 & 0001 Ismételten bitenkénti ÉS mûvelet, és az eredményünk 0, mivel csak az egyik bit 1, a másik 0, így a bitenkénti ÉS mûvelet eredménye is 0. Ezen az elven megcsináljuk a maradék 2 abronccsal is: tire3= tires >> 2 & 1;tire4= tires >> 3 & 1; Vagy a mi esetünkben konkrét példánkkal: tire3 = 0101 >> 2 & 0001 // azaz: --> 0001 & 0001 = 1tire4 = 0101 >> 3 & 0001 // azaz: --> 0000 & 0001 = 0 A teljes kódunk tehát: decode_tires(tires, &tire1, &tire2, &tire3, &tire4){ tire1 = tires & 1; tire2 = tires >> 1 & 1; tire3 = tires >> 2 & 1; tire4 = tires >> 3 & 1;} A konkrét példánkban a 4 változónk értéke a függvény lefutása után: tire1: 1tire2: 0tire3: 1tire4: 0 Ha összehasonlítjuk, ezek pont azok az értékek, mint amiket az encode függvényben megadtunk, tehát úgy tûnik jól dolgoztunk 2.3 encode_lightsEz a rész lesz a legrövidebb. Az egész lámpás rész pontosan ugyanarra a sémára épül mint az abroncsok. Az adott bitek ugyanazon a \"helyen\" lévõ lámpa adatait szolgáltatják, ugyanúgy 0 az ép, és 1 a sérült lámpák állapot azonosítója, és ugyanúgy 1-1 bit tárolja az értékeket.Maga a kód: encode_lights(light1, light2, light3, light4){ return light1 | light2 << 1 | light3 << 2 | light4 << 3;} Láthatjuk hogy halál pontosan ugyanaz mint az encode_tires, csak más változónevekkel. Így hát a decode függvény is ugyan az lesz, csak a változóneveket kell módosítanunk: decode_lights(lights, &light1, &light2, &light3, &light4){ light1 = lights & 1; light2 = lights >> 1 & 1; light3 = lights >> 2 & 1; light4 = lights >> 3 & 1;} Ha megnézzük látjuk, hogy teljesen ugyan az a séma, ugyan az a felépítés, minden megegyezik, ezért nem is írtam le újból ugyanazt, amit egyszer már kifejtettem.2.4 encode_panelsHáttér információ:A SA:MP 7 panelrõl tárol adatokat. Egy panel adatait 4 bit írja le. Nos, ennél több infóra nekünk most nincs szükségünk, az elõzõ kód alapján ezen kód is megérthetõ, így az encode verzióhoz nem is nagyon szeretnék magyarázatot fûzni. A különbség az encode_tires-hez képest, hogy itt 4-essével shiftelünk, mivel 1 panel adatait 4 bit tárolja.Maga a kód: encode_panels(flp, frp, rlp, rrp, windshield, front_bumper, rear_bumper){ return flp | frp << 4 | rlp << 8 | rrp << 12 | windshield << 16 | front_bumper << 20 | rear_bumper << 24;} Nézzünk egy decode verziót, ehhez a kódhoz. Alapjaiban ugyanazon a vonalon kell elindulnunk, mint a decode_tires esetében. Itt most 7 visszatérési értékünk lesz, és 1 bemeneti érték. Tehát így néz ki a függvény header: decode_panels(panels, &flp, &frp, &rlp, &rrp, &windshield, &front_bumper, &rear_bumper) A folytatásban megint csak az elõbbi verziót kell alapul vennünk, azonban itt lesz egy kis különbség. Egyértelmû, hogy mivel 1 panel adatait 4 bit tárolja, így a 4 bitet nekünk egyben kell lekérni, és egyben kell vele visszatérnünk. Megint csak a bitenkénti ÉS mûveletet fogjuk alkalmazni, azonban mivel 4 bitrõl van szó, így egy olyan számmal kell összehasonlítanunk, mely bináris alakban 1111. Ez a szám nem más, mint a 15. Hogy miért ez kell?Ha egy bitsorozat tartalmát ki szeretnénk másolni egy másik változóba, akkor a bitenkénti ÉS mûveletre vna szükségünk. A bitenkénti ÉS mûvelet akkor ad 1-et eredményül, ha mindkét bit 1, ha valamelyik 0, akkor a visszatérés már 0 lesz. Ergó tehát, ha egy csupa 1-esekbõl álló bitsorozattal vetjük össze az eredeti bitsorozatunkat, akkor lényegében lemásoljuk a sorozatot, egy másik változóba, hisz eredményül mindig az eredeti sozatot fogjuk visszakapni. Íme néhány példa: 0101 & 1111 = 01011111 & 1111 = 11110000 & 1111 = 00001011 & 1111 = 1011 Láthatjuk tehát, hogy ha csupa 1-est tartalmazó bitsorozattal hasonlítjuk össze az eredeti számunkat, akkor lényegében egy másolatot készítünk. Mivel nekünk erre a 4 bitre van szükségünk a 32-bõl, így pontosan ezt kell tennünk, ezen 4 bitet lemásolni egy másik változóba, hogy késõbb ezt majd egy másik függvénnyel még jobban darabokra szedhessük.Íme tehát akkor a kódunk elsõ sora: flp = panels & 15; Mivel 4-esével vizsgáljuk a biteket, így lényegében annyit kell még változtatnunk az elõzõ kódon(decode_tires), hogy az 1-esével növekvõ shiftelések helyett 4-esével shifteljünk. Azaz a 2. panel adatainak lekérdezése: frp = panels >> 4 & 15; Innen pedig már csak mindig további 4 shiftelést el kell végeznünk, és szépen végig haladhatunk a panels változó bitjein, és megkaphatjuk minden panel adatait külön-külön. rlp = panels >> 8 & 15;rrp = panels >> 12 & 15;windshield = panels >> 16 & 15;front_bumper= panels >> 20 & 15;rear_bumper = panels >> 24 & 15; Ezennel minden panel adatait megkaptuk. Ezután már akár a decode_tires függvényt felhasználva szét lehet szedni az adatokat darabokra, de akár egy saját névvel, és más változó nevekkel ellátott másik függvényt is létre lehet hozni a decode_tires mintájára.Tehát az egész kód egyben: decode_panels(panels, &flp, &frp, &rlp, &rrp, &windshield, &front_bumper, &rear_bumper){ flp = panels & 15; frp = panels >> 4 & 15; rlp = panels >> 8 & 15; rrp = panels >> 12 & 15; windshield = panels >> 16 & 15; front_bumper= panels >> 20 & 15; rear_bumper = panels >> 24 & 15;} Igazából a függvény ugyanarra a sémára épül mint a decode_tires, csak annyi a különbség, hogy 4 bitenként kapjuk az adatokat, nem pedig egyesével, mivel itt minden panelnek 4 adatát tudjuk majd még lekérdezni. Nem tudom lehet-e még ehhez a részhez hozzáfûzni valamit, szóval szerintem ugorjunk. 2.5 encode_doorsHáttér információ:A SA:MP képes minden jármû 4 \"ajtajának\" állapotát tárolni. Ezek az \"ajtók\"(Doors-t hogy fordíthatnám jobban?) konkrétan a motorháztetõ, csomagtartó, vezetõoldali ajtó, és anyósülés oldali ajtó. A 2 hátsó ajtó állapotát jelenleg még nincs lehetõségünk lekérdezni.Amikor lekérdezzük egy jármû \"ajtajának\" adatait, azt a SA:MP 4 bájtként(32 bit) adja vissza. Ebbõl a 4 bájtból 1 bájt(8 bit) jut minden egyes objektumra. Vagyis minden egyes objektum adatait 8 bit tárolja. Vagyis tárolhatná...A SA:MP csak az elsõ 3 bitet használja, a további bitek üresek lesznek, nincs funkciójuk, talán majd késõbb lesz. Az elsõ bit azt tárolja, hogy az objektum zárt[0], vagy nyitott[1](nem az jelenti, hogy bezárt, hanem becsukott/kinyitott) állapotban van-e. A második bit azt adja meg, hogy az objektum ép[0] vagy sérült[1]. A 3. bit arról tájékoztat, hogy az adott objektum rajta van-e még a jármûvön[0] vagy már leesett[1]. A többi bit mint mondtam üres. Ez majd a decode függvénynél lesz fontos.Nézzük tehát a konkrét kódot: encode_doors(bonnet, boot, driver_door, passenger_door, behind_driver_door, behind_passenger_door){ #pragma unused behind_driver_door #pragma unused behind_passenger_door return bonnet | boot << 8 | driver_door << 16 | passenger_door << 24;} A 2 hátsó ajtóval nem foglalkozunk, mivel azok jelenleg még nem támogatottak ugyebár, de a kód készítõje gondolt a jövõre is, vagy fogalmam sincs miért adta meg ezt a két paramétert is, hisz ígyis-úgyis módosítani kell a kódon... Ha behoznak még 2 ajtót, akkor az 1 bájtos tárolással már 6 bájt lenne, de 1 változó maximum 4 bájt lehet, tehát a kódot úgyis módosítani kell, ha meg arra gondoltak, hogy legalább a paramétereket nem kell majd szegény scriptereknek minden sorban átírni, az megint hülyeség, hisz ki adna értéket olyan paraméternek, amely nincs használva?...Nos mindegy, nem a kritizálásért vagyunk most itt. Ha megnézzük, a kód 8-as shiftelést használ, mivel egy objektum adatait 8 bit tárolja. A mûködési elv ugyanaz, mint az elõzõ encode függvényeknél, így erre nem kívánok megint kitérni.Nézzük azonban hogyan írnánk ehhez egy decode függvényt. Mikor megkérdeztem errõl ismerõsömet, õ azt mondta, ugyanúgy mint az elõzõ, decode_panels függvénynél, csak 8-as shiftelést használunk, és 15 helyett 255-el végezzük a bitenkénti ÉS mûveletet. 255-el? minek?No erre is ki fogok térni késõbb, de haladjunk a szokásos módon. 1 bemeneti paraméterünk van, és 4 kimeneti, vagyis a header rész a következõ: decode_doors(doors, &bonnet, &boot, &driver_door, &passenger_door) Ezzel meg is vagyunk, itt nem látok különösebb magyarázni valót. Én most nem fogok törõdni a 2 nem létezõ ajtóval, ha egy jövõbeni verzióban netán azokat is behoznák mint lekérdezhetõ objektum, akkor ígyis-úgyis módosítani kell majd a kódot, addig meg úgysincs rá szükségünk.Nos, mint az eddigi decode függvényeknél, elsõ feladatunk egy shiftelés mentes bitenkénti ÉS mûvelet elvégzése. Az egyik megoldás a következõ: bonnet = doors & 255; Lehet így is. Azonban véleményem szerint felesleges itt egy ekkora számmal \"szenvednünk\". Miért használjunk ekkora számot, és ezáltal bonyolítsuk feleslegesen a kódunkat, ha nekünk a 8 bitbõl csak az elsõ 3 lesz ami adatot tárol? Bõven elegendõ egy olyan szám, amely bináris alakja 111. Ez nem más, mint a 7. Tehát a következõ megoldás ugyanúgy helyes: bonnet = doors & 7; A két kód pontosan ugyanazt az eredményt fogja visszaadni, semmi különbség a kettõ között. Csupán elsõ esetében nagy számmal dolgozunk, még második verzió esetén csak akkorával, amekkora szükséges.A kód többi részében csak 8-as shifteléssel végigcammogunk a doors változó többi bitjén is: boot = doors >> 8 & 7;driver_door = doors >> 16 & 7;passenger_door = doors >> 24 & 7; A teljes kód egyben: decode_doors(doors, &bonnet, &boot, &driver_door, &passenger_door){ bonnet = doors & 7; boot = doors >> 8 & 7; driver_door = doors >> 16 & 7; passenger_door = doors >> 24 & 7;} A kód pont ugyanazon az elven épül fel, mint az összes többi decode függvényünk, csak épp a benne használatos értékek az \"ajtó\" objectumok adatstruktúrájához vannak igazítva.2.6 Egyéb ötletek gyakorlati alkalmazásra, hasznosításraJoggal felmerülhet a kérdés, hogy ennyi? Emiatt rágtuk végig magunkat ezen néhány tízezer karakteren? Nem. Nem ennyi. A SA:MP-ban rengeteg felhasználási módja lehet még a bitmûveleteknek. Mutatok is néhány dolgot, mire jók még.2.6.1 RGBA->HEX; HEX->RGBA konvertálásokNos, bármily \"hihetetlen\", erre is használható. Miért is? Nos, 16-os számrendszerben 0-9, valamint A-F \"szánjegyek\" léteznek. A hexadecimális számjegy decimális értékben 0-15 lehet. Ha megnézzük bináris értékeket, 0-15 az 4 bittel leírható, hiszen a 15 az bináris alakban 1111. De ha józan paraszti ésszel belegondolunk is. 16 az 24. Így tehát 1 hexadecimális érték, az 4 bináris értékkel írható le. Ebbõl már sejthetõ, hogy itt is shiftelésre lesz szükségünk. Naná Mivel 4 bit ír le egy hexadecimális számjegyet, így 4-esével kell majd shiftelnünk.Mi is az az RGBA?RGBA: A színek olyan meghatározási módja, melyben a 3 alapszín([r]ed, [g]reen, lue) mellett egy 4. érték, az úgynevezett [a]lpha is szerepel, amely az áttetszõséget adja meg. A legtöbb program(pl. paint, PS), és pl. a css(külön függvénnyel!) is elfogadja ezen értékeket decimális számokként(0-255) is, a SA:MP-ban azonban hexadecimális alakban kell megadnunk. Bármily furcsa a következõ 4 sor pont ugyan azt jelenti:\"Normál\" RGB alak(pl. paint, photoshop): Piros: 0, Zöld: 255, Kék: 255CSS alak(függvénnyel): rgba(0, 255, 255, 1);Pawn alak(1): 0x00FFFFFFPawn alak(2): 16777215 Ó igen, a Pawn-ban decimális számokként is megadhatjuk a színeket, hisz a program mindent bináris számrendszerbe fog átszámolni. De akár bináris alakban is megadhattuk volna:Pawn alak(3): 0b111111111111111111111111 Az, hogy mi a Pawn-nak hexadecimális alakban szoktuk megadni, annak az egyetlen oka, hogy így a leginkább \"átlátható\", és ez egy amúgy is elfogadott módja (mármint a hexadecimális számmal való megadás) a színek leírásának a számítástechnikában.Nos, mivel 1 szín 0 és 255 közötti értéket tartalmaz a piros, zöld és kék színekbõl, ez hexadecimálisan 2 számjeggyel leírható: 00-FF. Az FF értéke 255, aki nem hiszi, számoljon utána (162-1)2 hexadecimális számjegy az pedig 8 bináris számjeggyel leírható(00000000-11111111). Így már láthatjuk, hogy 8-as shiftelésre lesz szükségünk. Másra nincs is szükségünk, az RGBA->HEX konverter elkészítéséhez.stock RGBAToHex( r, g, b, a ){return a | b << 8 | g << 16 | r << 24;} Az, hogy miért fordított sorrendben kellett megadnunk az értékeket, az megint csak azzal magyarázható, hogy a biteket hétköznapi értelemben véve visszafelé számozzuk.Egy ellentét, azaz HexToRGBA függvény megírása sem túl bonyolult, ugyan ezt kell csinálnunk, csak visszafelé.stock HexToRGBA( colour, &r, &g, &b, &a ){a = colour & 0xFF;b = (colour >> 8 ) & 0xFF;g = (colour >> 16 ) & 0xFF;r = (colour >> 24 ) & 0xFF;} A 0xFF-et természetesen decimális értékkel is megadhatjuk(255).No akkor lehet ugye RGBA-t ARGB-be is konvertálni, az így néz ki:RGBAToARGBHEX(r,g,b,a){return (r & 0xff) << 24 | (g & 0xff) << 16 | (b & 0xff) << 8 | a & 0xff;} Ez máris egy újabb felhasználási módja a bitmûveleteknek. De, mutatok még 2.6.2 Boolen változók helyett IntegerAz al-al-alcímet elolvasva joggal felmerülhet a kérdés, hogy ez meg hogy jön ide. Nos ez úgy jön ide, hogy most egy kis memóriatakarékosságról fogok itt regélni. Adhattam volna azt is címnek, hogy miért ne használjunk boolean változókat, ha legalább 5 igaz/hamis értéket el akarunk tárolni.Nos akkor egy kis memórialecke. Azt már tisztáztuk, hogy az integer változók 32 bitesek, vagyis 4 bájtot foglalnak le a memóriában maguknak. A boolean típusú változókról azonban még nem volt szó. Mivel 1 értéket tárolnak csak, az is csak igaz vagy hamis lehet, így joggal gondolhatnánk, hogy 1 bit a mérete. Azonban nem...1 boolean változó 8 bitet, azaz 1 bájtot foglal le magának a memóriában. Hogy ez miért így van, az a számítógépek memóriahasználatával magyarázható. Manapság nincs mód arra, hogy a memóriában egyetlen bitet módosítsunk, csak 8-as \"csomagokban\", vagyis bájtonként tudjuk ezt megtenni. Hogy ez miért van így, azt most nem fogom itt fejtegetni Nos, ha megnézzük tehát, a boolean változó egy nagyon-nagyon pazarló megoldás. Hiszen 1 darab boolean változó 1 bájtot foglal, 4 darab boolean változó esetén ez már 4 bájt, ami megegyezik 1 integer változó méretével. Azonban, még a 4 boolean változóban 4 igaz/hamis érték kerülhet tárolásra, addig egy integerben 32 igaz/hamis értéket is eltárolhatunk.Bizony, 32 igaz/hamis érték. Hiszen 1 integer változó 32 bitbõl áll, ha minden bitet hasznosítunk, akkor a bitmûveletek felhasználásával 32 igaz/hamis értéket is eltárolhatunk. Összehasonlítva:32 darab boolean változó mérete 32 bájt.1 integer változó mérete: 4 bájt. Vagyis 32 boolean változó esetén nyolcad annyi memóriánkba kerül, ha egy darab integerben tároljuk el az értékeket. Persze 32 booleanra ritkán van szükség, de egy RP modban simán elõfordulhat. Gondoljunk csak bele... Van 32 boolean-tömbös változónk, ez a maximum slotszámot(1000) figyelembe véve: 32*1000 byte azaz 32000 byte ami ~31 kilobyte. Ha ezt egyetlen integer-tömbbel oldanánk meg: 4*1000 byte, azaz 4000 byte, ami ~3,9 kilobyte. Azért így nézve már nem annyira \"elhanyagolandó\" az a különbség szerintem =)Akár írhatnánk erre külön egy függvényt, mely az adott bit értékével tér vissza. Nem is bonyolult. Így már azok számára is használhatóak a bitmûveletek, akik nem értenek hozzá, és rengeteg memóriát takaríthatnak meg.Íme egy függvény az adatok kiolvasására:stock GetData(integer, bit){ return integer >> bit & 1; //vagy az 1 helyett akár 0x01 alakban is megadhatjuk, nincs jelentõsége} És a párja, adat írásra:stock SetData(&integer, bit, value){ integer |= value << bit; return 1;} Persze ezt még lehetne csinosítani, hogy pl. ne 0-tól hanem 1-tõl számoljuk a biteket, vagy hogy 31(vagy 32 ha 1-tõl számolunk) felett 0 legyen a return értéke, esetleg a value-re is rakhatnánk ellenõrzést, hogy 0-1-e az értéke, vagy más, de ilyenekkel most nem törõdtem.2.6.3 Több érték, egy integerbeNa ez az a rész, ami véleményem szerint már csak a nagyon megrögzött memóriafelszabadítók szerint nem lesz felesleges Amennyiben nincs szükségünk túl nagy számokra(pl. csak 0-15, vagy akár csak 0-7, de akár még a 0-255 is megfelel), akkor akár egy integerbe is tehetjük õket. Hogyan? Egyszerû. Csoportosítjuk a biteket. Tegyük fel, hogy maradván egy RP szervernél, el akarjuk tárolni ûbergigantikusan memóriakímélõ megoldással a játékos házának számát(ez legyen mondjuk 0-100 közötti szám), a játékos frakcióját(ez legyen 0-15 között), a játékos rangját(0-7 között), hogy a telefonja be van-e kapcsolva(0-1), a játékos szolgálati jármûvének id-jét(legyen 0-255 között), és az alapmunkáját(0-15 között). Ez is megoldható, egyetlen integerben, és ismételten 1000 slottal számolva máris megspóroltunk 4*4*1000+1*1000 byte-t(5 integer, és 1 boolean helyett csupán 1 integert használunk), ami ~16,6 kilobyte. Azért nem mindegy az, hogy 20,5 kb-ot vagy 4 kb-ot használunk szerintem... Ezt a következõ módszerrel valósíthatnánk meg:#define DATA_HOUSE 0#define DATA_FACTION 1#define DATA_RANK 2#define DATA_PHONESTATE 3#define DATA_DUTYCAR 4#define DATA_JOB 5stock GetData(playerinfo, data){ switch(data) { case DATA_HOUSE: return playerinfo & 127;//7 bit case DATA_FACTION: return playerinfo >> 7 & 15;//4 bit case DATA_RANK: return playerinfo >> 11 & 7;//3 bit case DATA_PHONESTATE: return playerinfo >> 14 & 1;//1 bit case DATA_DUTYCAR: return playerinfo >> 15 & 255;//8 bit case DATA_JOB: return playerinfo >> 23 & 15;//4 bit}return -1;}stock SetData(&playerinfo, data, value){ switch(data) { case DATA_HOUSE: { playerinfo = playerinfo & ~127 | value; }//7 bit (0-127 közötti maximálisan) case DATA_FACTION: { playerinfo = playerinfo & ~(15 << 7) | value << 7; }//4 bit (0-15 között) case DATA_RANK: { playerinfo = playerinfo & ~(7 << 11) | value << 11; }//3 bit (0-7 között) case DATA_PHONESTATE: { playerinfo = playerinfo & ~(1 << 14) | value << 14; }//1 bit (0-1) case DATA_DUTYCAR: { playerinfo = playerinfo & ~(255 << 15) | value << 15; }//8 bit (0-255 között) case DATA_JOB: { playerinfo = playerinfo & ~(15 << 23) | value << 23; }//4 bit (0-15 között) } return -1;}stock SetDatas(&playerinfo, house, faction, rank, phonestate, dutycar, job){playerinfo |= house | (faction << 7) | (rank << 11) | (phonestate << 14) | (dutycar << 15) | (job << 23);} Ez már azért elég morbid, ezt inkább csak érdekességképp mutattam, de éppenséggel ez is használható lenne, csak ehhez már kell egy kis õrültség 2.7 Második fejezet végeNos, láthattuk hogyan is mûködik mindazon sok \"marhaság\", amit az elsõ fejezetben elméletként átvettünk. Láthattuk hogyan mûködnek a jármûvek paneljének adataival babráló encode függvények, valamint megmutattam, hogyan írhatunk hozzájuk decode verziókat. Láthattunk még példát egyéb gyakorlati hasznosításra, kicsit megkoccoltuk a hexadecimális számok világát is, majd szóba került egy kis memóriatakarékosság. Végül mutattam egy összetettebb, elvetemült példát is, a bitmûveletek hasznosítására.3. Összegzés, végszó Úgy gondolom, hogy elég részletesen körbe jártuk a témát. Megnéztük a bitmûveletek elméleti, és gyakorlati oldalát is, láthattunk gyakorlati példákat. Nem kell elsõre megérteni ezt az egészet, sõt... Elegendõ ha egyszer elolvassuk, emésztjük kicsit, majd késõbb megint elõvesszük, akkor netán már kicsit többet is megértünk belõle. Nem egy kihagyhatatlan része a Pawno-nak, de azért van annak haszna, ha az ember jártas ebben a témában is, bár sokak szerint nem éri meg a sok fáradtságot, és idõt, mely a megértéséhez, begyakorlásához, használatának elsajátításához szükséges. Próbáltam mindent részletesen kifejteni. A bonyolultabb dolgokhoz sokat magyarázni, az egyszerûbbek esetén csak visszautalni az alapokhoz. Sok dolgot talán már túl is magyaráztam. Ezt a leírást nem 1 nap alatt készítettem el, kb. 1 hónapja írtam meg az elméleti oldalát, most az elmúlt pár napban pedig a gyakorlatot. Esténként 1-2 órát szántam rá csupán, és mivel ilyenkor már nem vagyok épp a toppon, elképzelhetõ, hogy néhány helyen helyesírási, vagy elvi hibát vétettem. Amennyiben valaki ilyet találna, kérem szóljon, és korrigálni fogom. Nos, azt hiszem nincs is más hátra, mint hogy sok szerencsét kívánjak a bitmûveletek elsajátításához, és megosszam a forrásokat. Remélem néhányan hasznosnak találják, eme igen hosszúra sikerült leírást.4. Források: Internet:WikipédiaSA:MP WikiSA:MP hivatalos fórumKönyv, e-book:Pohl László - A programozás alapjai(BME - Elektronikus Eszközök Tanszéke, Budapest, 2010)Siroki László - C programozás - Bitmûveletek jegyzeteˆ Anthony, 2012 - Minden jog fenntartva.A leírás egészben, vagy részleteiben szabadon terjeszthetõ, ameddig egyértelmûen fel van tüntetve az eredeti készítõ, valamint hogy honnan is származik. « Utoljára szerkesztve: 2012. augusztus 01. - 10:53:54 írta Anthony » Naplózva
new var = 0b1011;
encode_tires(tire1, tire2, tire3, tire4){ return tire1 | tire2 << 1 | tire3 << 2 | tire4 << 3;}
encode_tire(1,0,1,0);
tire1 | tire2 << 1 | tire3 << 2 | tire4 << 3
1 | 0 << 1 | 1 << 2 | 0 << 3
0001 | 0000 << 1 | 0001 << 2 | 0000 << 3
0000 << 1 = 00000001 << 2 = 01000000 << 3 0000
0001 | 0000 | 0100 | 0000
0001 | 0000 = 0001
0001 | 0100 | 0000
0001 | 0100 = 0101
0101 | 0000
0101 | 0000 = 0101
0100 | 0000 << 1 | 0001 << 2 | 0000 << 3v0001 | 0000 | 0100 | 0000v0001 | 0100 | 0000v0101 | 0000v0101
decode_tires(tires, &tire1, &tire2, &tire3, &tire4)
tire1 = tires & 1;
tire1 = 0101 & 0001
tire2= tires >> 1 & 1;
tire2 = 0101 >> 1 & 0001vtire2 = 0010 & 0001
tire3= tires >> 2 & 1;tire4= tires >> 3 & 1;
tire3 = 0101 >> 2 & 0001 // azaz: --> 0001 & 0001 = 1tire4 = 0101 >> 3 & 0001 // azaz: --> 0000 & 0001 = 0
decode_tires(tires, &tire1, &tire2, &tire3, &tire4){ tire1 = tires & 1; tire2 = tires >> 1 & 1; tire3 = tires >> 2 & 1; tire4 = tires >> 3 & 1;}
tire1: 1tire2: 0tire3: 1tire4: 0
encode_lights(light1, light2, light3, light4){ return light1 | light2 << 1 | light3 << 2 | light4 << 3;}
decode_lights(lights, &light1, &light2, &light3, &light4){ light1 = lights & 1; light2 = lights >> 1 & 1; light3 = lights >> 2 & 1; light4 = lights >> 3 & 1;}
encode_panels(flp, frp, rlp, rrp, windshield, front_bumper, rear_bumper){ return flp | frp << 4 | rlp << 8 | rrp << 12 | windshield << 16 | front_bumper << 20 | rear_bumper << 24;}
decode_panels(panels, &flp, &frp, &rlp, &rrp, &windshield, &front_bumper, &rear_bumper)
0101 & 1111 = 01011111 & 1111 = 11110000 & 1111 = 00001011 & 1111 = 1011
flp = panels & 15;
frp = panels >> 4 & 15;
rlp = panels >> 8 & 15;rrp = panels >> 12 & 15;windshield = panels >> 16 & 15;front_bumper= panels >> 20 & 15;rear_bumper = panels >> 24 & 15;
decode_panels(panels, &flp, &frp, &rlp, &rrp, &windshield, &front_bumper, &rear_bumper){ flp = panels & 15; frp = panels >> 4 & 15; rlp = panels >> 8 & 15; rrp = panels >> 12 & 15; windshield = panels >> 16 & 15; front_bumper= panels >> 20 & 15; rear_bumper = panels >> 24 & 15;}
encode_doors(bonnet, boot, driver_door, passenger_door, behind_driver_door, behind_passenger_door){ #pragma unused behind_driver_door #pragma unused behind_passenger_door return bonnet | boot << 8 | driver_door << 16 | passenger_door << 24;}
decode_doors(doors, &bonnet, &boot, &driver_door, &passenger_door)
bonnet = doors & 255;
bonnet = doors & 7;
boot = doors >> 8 & 7;driver_door = doors >> 16 & 7;passenger_door = doors >> 24 & 7;
decode_doors(doors, &bonnet, &boot, &driver_door, &passenger_door){ bonnet = doors & 7; boot = doors >> 8 & 7; driver_door = doors >> 16 & 7; passenger_door = doors >> 24 & 7;}
\"Normál\" RGB alak(pl. paint, photoshop): Piros: 0, Zöld: 255, Kék: 255CSS alak(függvénnyel): rgba(0, 255, 255, 1);Pawn alak(1): 0x00FFFFFFPawn alak(2): 16777215
Pawn alak(3): 0b111111111111111111111111
stock RGBAToHex( r, g, b, a ){return a | b << 8 | g << 16 | r << 24;}
stock HexToRGBA( colour, &r, &g, &b, &a ){a = colour & 0xFF;b = (colour >> 8 ) & 0xFF;g = (colour >> 16 ) & 0xFF;r = (colour >> 24 ) & 0xFF;}
RGBAToARGBHEX(r,g,b,a){return (r & 0xff) << 24 | (g & 0xff) << 16 | (b & 0xff) << 8 | a & 0xff;}
32 darab boolean változó mérete 32 bájt.1 integer változó mérete: 4 bájt.
stock GetData(integer, bit){ return integer >> bit & 1; //vagy az 1 helyett akár 0x01 alakban is megadhatjuk, nincs jelentõsége}
stock SetData(&integer, bit, value){ integer |= value << bit; return 1;}
#define DATA_HOUSE 0#define DATA_FACTION 1#define DATA_RANK 2#define DATA_PHONESTATE 3#define DATA_DUTYCAR 4#define DATA_JOB 5stock GetData(playerinfo, data){ switch(data) { case DATA_HOUSE: return playerinfo & 127;//7 bit case DATA_FACTION: return playerinfo >> 7 & 15;//4 bit case DATA_RANK: return playerinfo >> 11 & 7;//3 bit case DATA_PHONESTATE: return playerinfo >> 14 & 1;//1 bit case DATA_DUTYCAR: return playerinfo >> 15 & 255;//8 bit case DATA_JOB: return playerinfo >> 23 & 15;//4 bit}return -1;}stock SetData(&playerinfo, data, value){ switch(data) { case DATA_HOUSE: { playerinfo = playerinfo & ~127 | value; }//7 bit (0-127 közötti maximálisan) case DATA_FACTION: { playerinfo = playerinfo & ~(15 << 7) | value << 7; }//4 bit (0-15 között) case DATA_RANK: { playerinfo = playerinfo & ~(7 << 11) | value << 11; }//3 bit (0-7 között) case DATA_PHONESTATE: { playerinfo = playerinfo & ~(1 << 14) | value << 14; }//1 bit (0-1) case DATA_DUTYCAR: { playerinfo = playerinfo & ~(255 << 15) | value << 15; }//8 bit (0-255 között) case DATA_JOB: { playerinfo = playerinfo & ~(15 << 23) | value << 23; }//4 bit (0-15 között) } return -1;}stock SetDatas(&playerinfo, house, faction, rank, phonestate, dutycar, job){playerinfo |= house | (faction << 7) | (rank << 11) | (phonestate << 14) | (dutycar << 15) | (job << 23);}
stock Biznis_SetIcon(biznisid, icon){g_iBiznisFlags[biznisid] &= 0x81FFFFFF; // Értéket ide írod a szám helyére.g_iBiznisFlags[biznisid] = icon << 25 | g_iBiznisFlags[biznisid]; // Aztán beleírjuk újra az ikont, és ez csak ezt a részét írja felül. (Max Icon ID 63, 6bit)}#define Biznis_GetIcon(%1) \\ (g_iBiznisFlags[(%1)] >>> 25 & 0x3F)
stock ShiftRGBAToHex(color)return (color >>> ;
printf(\"%x\", ShiftRGBAToHex(0xFFFFFFFF));printf(\"%x\", ShiftRGBAToHex(0x8B0000FF)); //sötét piros
FFFFFF8B0000
Írtam ennél egyszerûbb változatot is: stock RGBAToHex( r, g, b, a ){return a | b << 8 | g << 16 | r << 24;} Ez: stock ShiftRGBAToHex(color)return (color >>> ; Használata meg egyszerû: printf(\"%x\", ShiftRGBAToHex(0xFFFFFFFF));printf(\"%x\", ShiftRGBAToHex(0x8B0000FF)); //sötét piros Az alábbit fogja kiprintelni nekünk: FFFFFF8B0000
Illetve egy integer típúsú változó -2,147,483,648 és 2,147,483,647 közötti értéket vehet fel.[/quote]Igaz, elírtam, javítva. Illetve a gumikat, ajtókat, világítást, illetve a panelt a SA-MP képes decimális számban is elmenti illetve beolvasni.[/quote]A SA:MP hál istennek korlátok nélkül képes dec-bin-hex-oct konvertálásokra, mivel amit mi decimális integernek látunk, valójában az is bináris szám, csak a mi kedvünkért konvertálja \"fel\" a program, a könnyebb kezelhetõség érdekében. Így változót kiírathatunk 4 féle képpen is: printf(\"%b, %o, %d, %x\", integer, integer, integer, integer); Ergó 4 alakban is \"elmenthetjük\", de integerként a legegyszerûbb beolvastatni. (bár most így belegondolva, fogalmam sincs, h a strval képes-e a decimálison túl más értékeket is kezelni, de kétlem). Szerintem ide még besorolhatnád a \"char\" típusú tömböket is, amely lecsökkenti ezen értékek számát 0 és 255 közé. Szóval a cellák méretét 1bájtnyira csökkenti, magyarán 8bites-be.[/quote]Hú, erre nem is gondoltam. Annyit foglal mint egy boolean, és 8 igaz/hamis értéket el lehet benne tárolni. Igaz, nem olvastam el, mivel most keltem, viszont szép leírás. [/quote] Szép leírás, csak az a baj hogy én még nem értek belõle semmit sem. [/quote]Köszönöm
Illetve a gumikat, ajtókat, világítást, illetve a panelt a SA-MP képes decimális számban is elmenti illetve beolvasni.[/quote]A SA:MP hál istennek korlátok nélkül képes dec-bin-hex-oct konvertálásokra, mivel amit mi decimális integernek látunk, valójában az is bináris szám, csak a mi kedvünkért konvertálja \"fel\" a program, a könnyebb kezelhetõség érdekében. Így változót kiírathatunk 4 féle képpen is: printf(\"%b, %o, %d, %x\", integer, integer, integer, integer); Ergó 4 alakban is \"elmenthetjük\", de integerként a legegyszerûbb beolvastatni. (bár most így belegondolva, fogalmam sincs, h a strval képes-e a decimálison túl más értékeket is kezelni, de kétlem). Szerintem ide még besorolhatnád a \"char\" típusú tömböket is, amely lecsökkenti ezen értékek számát 0 és 255 közé. Szóval a cellák méretét 1bájtnyira csökkenti, magyarán 8bites-be.[/quote]Hú, erre nem is gondoltam. Annyit foglal mint egy boolean, és 8 igaz/hamis értéket el lehet benne tárolni. Igaz, nem olvastam el, mivel most keltem, viszont szép leírás. [/quote] Szép leírás, csak az a baj hogy én még nem értek belõle semmit sem. [/quote]Köszönöm
printf(\"%b, %o, %d, %x\", integer, integer, integer, integer);
Szerintem ide még besorolhatnád a \"char\" típusú tömböket is, amely lecsökkenti ezen értékek számát 0 és 255 közé. Szóval a cellák méretét 1bájtnyira csökkenti, magyarán 8bites-be.[/quote]Hú, erre nem is gondoltam. Annyit foglal mint egy boolean, és 8 igaz/hamis értéket el lehet benne tárolni. Igaz, nem olvastam el, mivel most keltem, viszont szép leírás. [/quote] Szép leírás, csak az a baj hogy én még nem értek belõle semmit sem. [/quote]Köszönöm
Igaz, nem olvastam el, mivel most keltem, viszont szép leírás. [/quote] Szép leírás, csak az a baj hogy én még nem értek belõle semmit sem. [/quote]Köszönöm
Szép leírás, csak az a baj hogy én még nem értek belõle semmit sem. [/quote]Köszönöm
Amúgy énmég erre figyeltem fel, hogy ezt még elírtad. -2^32(-2,147,483,648) és 2^32-1(2,147,483,647) Csak a 31.-en, mivel nem elõjel nélküli.
Még azt megkérdezném, hogy hogyan tárolja maga a gép azt, hogy most ez elõjeles, vagy nem.Azt értem, hogy az utolsó bit jelenti azt, de ha te most lefoglalsz egy elõjel nélkülit (unsiged int pl), akkor azt most a gép honnan fogja tudni, hogy ott az utolsó bit nem az elõjelet jelenti?Tárol erre még valahol valamit, hogy tudja, hogy nem az elõjel az utolsó bit, hanem azis \"bele tartozik\".? [/quote]A Pawn mint tudjuk, egy eléggé korlátozott nyelv. Engem is érdekelt ez a kérdés, ezért utána kerestem, és SA:MP wikin (ITT) meg is találtam a választ. A Pawn-ban nincs lehetõségünk unsigned int használatára. (csupán meg van említve, hogy létezik ilyen is). Ellenben a C-ben pl. elég sokféle változó létezik: short intunsigned short intunsigned intintlong intsigned charunsigned charfloatdoublelong double Azt most csak példának mutattam, mert az elv ugyanaz. Létezik egy úgynevezett szimbólumtábla, ami a következõ adatokat tárolja: szimbólum neveszimbólum attribútumai:definíció adataitípustárgyprogram-beli címdefiníció helye a forrásprogrambanszimbólumra hivatkozások a forrásprogramban Tehát innen tudja a program, hogy egy változó milyen típusú, így pl. az X-edik bit(jelen esetünkben pl. egy C int esetén, a 32. bit) MSB, vagy nem.
short intunsigned short intunsigned intintlong intsigned charunsigned charfloatdoublelong double