Egyedi típusok defíniálása



A programozás során természetesen előfordulhat hogy az alapvető típusok (mint az int, float, ...) nem nyújtanak számunkra elég lehetőséget, vagy sokkal nehezebben kivitelezhető programot adnak eredményül.

Ilyenkor lehetőségünk van ún. egyedi típusok alkalmazására - amiknek mi adjuk meg a jellegét - természetesen az alaptípusokra építkezve.

1, Tömb/vektor
    1a, Mátrixok
    1b, A tér
    1c, Tetszőleges n dimenziós tömb
2, Struktúra
    2a, Típusdefiníció
    2b, A struktúrák felvétele
    2c, A struktúramutató
    2d, Union
    2e, Flexibilis struktúra
3, Az enumeráció
4, Álnév (alias)
5, Halmazok
    5a, Halmazelemekkel kapcsolatos műveletek
    5b, Halmazok közötti műveletek


1, Tömb/vektor

Legyen a feladat 100db szám kiátlagolása. Ebben az esetben az már egy jó gondolat hogy for ciklussal hajtsuk végre az összegzést, de a számok tárolása kérdéses lehet - pl. vegyünk fel 100db változót így: int a1, a2, a3, ... ? Ennek semmi értelme nem volna, hiszen a for ciklusban nem tudnánk használni a nevüket ilyen módon. Erre nyújt megoldást a tömb.

A tömb logikája hogy egymás utáni memóriaterületekre azonos típusú változók kerülnek - a tömb pedig az első memóriacímét takarja (legyen P). Mivel tudjuk hogy egy mérete pl. 4 byte - ha a 25.-dik elemre van szükségünk akkor annak a címe:

[25. elem címe] = P + 4 * (25 - 1)
[1. elem címe] = P

Azért kell kivonni egyet, mert már ha az 1.-nél állunk akkor 1 + 25 = 26. De ennek van egy más megközelítése is, ami számunkra érdekesebb - a C/C++ megközelítés:

Kezdjük el az elemeket nullától számozni, így a [25. elem] igazából a 26.-dik lesz, de ez majd lényegesen egyszerűsíti a dolgunkat, és a programozó meg majd számol nullától:

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25] -> ez 26 db számjegy

[25. elem címe] = P + 4 * 25
[0. elem címe] = P

Ezek után belátható hogy a C/C++ nyelvben miért lesz a tömb mindig nullától indexelt. A műveleteknél már elöljáróban ismertetve lett a tömb operátora - ám példa nem volt rá. Íme akkor egy tömbdeklaráció és a fentebb említett példa:

int tomb[100], i;
float atlag = 0;

for (i = 0; i < 100; i++)
  atlag += tomb[i];

atlag /= 100;

A példában a for ciklus az i számot 0-99 értékek között növeli, és a tömb [i. dik elemét] hozzáadja az átlaghoz. Ez után hogy tényleg átlag legyen leosztja 100-zal az eredmény, mivel 100 számot adtunk össze.

Tömbökből is lehet konstansot készíteni, illetve lehet nekik kezdőértéket adni, pl.:

const int szamok[] = {0, 1, 2, 3, 4};



int tomb[100] = {0};

Az első példa esetében nem adtuk meg az elemszámot - ilyenkor ezt a C/C++ eldönti magától (kezdőérték esetén csak).
A második példa a tömb mind a 100 elemét ki fogja nullázni - így is lehet kezdőértéket adni.

Mivel a tömb lényegében egy szám n-es, ezért vektornak is hívják - pl. egy float v[3] = {-1, 2, 0} valóban remekül reprezentálja a tér egy vektorát.

A C/C++ nyelvben a tömb ~= a mutatóval is. Mint ezt már korábban láthattuk a szöveg tárolásánál, a szöveg egy char[...] változó, de ha nem ismerjük a hosszát akkor char*. Ennek ellenére mind kettő indexelhető a fentebb látható módon, illetve mind a kettőn működnek a mutatóműveletek. Ez később fontos lesz a dinamikus tömböknél.

Bárhogy is használjuk a tömböket oda kell figyelni a túlindexelésre. A tomb[-1] és a tomb[100] memóriahibát fog okozni, mivel olyan területre kér hozzáférést amire a programnak nincs (védett mód) - így a programot vagy bezárja az operációs rendszer, vagy kivételt generál benne. A C jellemzője az előbbi, a C++ jellemzője az utóbbi - egyik sem nyerő a programra nézve.
Mindig figyelni kell tehát hogy csak a határok között dolgozzunk a tömbbel ([0..99] a példában).


Megjegyzés: Mivel a tömb memóriafolytonos, és összecserélhető a mutatóval érvényesek, és ekvivalensek lesznek az alábbi műveletek:

int tomb[100] = {0};
int* tomb_mutato = tomb;

if (tomb[97] == *(tomb_mutato + 97))
  tomb_mutato[97] = *(tomb + 97);

Ez a pár sor nem csinál semmit, csak szórakozik a tömb különböző hivatkozásaival.

Amikor felvesszük a mutatót, azért nem kell az & jel a tomb elé, mert az már önmagában véve egy mutató.

A (tomb_mutato + 97) azt jelenti hogy 97 * sizeof(int) értékkel tolja el a tomb_mutatoban lévő memóriacímet - mintha indexelnénk, így következésképpen meg kell egyeznie a tomb[97]-nek a (tomb_mutato + 97) címen lévő elemmel, ami a *(tomb_mutato + 97).

Mivel ez úgyis teljesülni fog, az alsó sor arra mutat példát, hogy annyira ekvivalens a mutató, és a tömb a C/C++ nyelvben, hogy a két megadási mód fordítva is használható.

Ez a kódrészlet csak a hivatkozásokat mutatja be, a tomb elemei nem fognak változni.

1a, Mátrixok

A mátrix lényegében egy számhalmaz, ahol a számokat x oszlopba és y sorba rendezzük. pl. (x = 3, y = 4):

5 6 2
-1 4 2
0 3 1
2 4 -5

A C/C++ ilyen adatszerkezet kialakítására is képes - mindössze két tömböt kell használni egymásban:

"egy olyan tömböt, aminek az elemei számokból álló tömbök"

 

Ez után akkor nézzünk erre is egy példát:

int szamok[4][25], x, y; //4 * 25 = 100
float atlag = 0;

for (y = 0; y < 4; y++)
  for (x = 0; x < 25; x++)
    atlag += tomb[y][x];

atlag /= 100;

Tippek a mátrixokhoz:

  1. Mindig [sor][oszlop] alakban adjuk meg őket - mind deklarálásnál, mind a hivatkozásnál

  2. Mivel [sor][oszlop] szerkezetet használunk, ezért mindig a [sor] for ciklusa van kívül, és az [oszlop] for ciklusa van belül

  3. Célszerű [y][x] koordinátákkal jelölni az irányokat, mint a koordináta-rendszer tengelyeit

Végül néhány konstansmátrix:

int szamok[2][3] = {{1, -2, 3}
                    {4, 5, -6}};


int szamok[2][3] = {1, -2, 3, 4, 5, -6};


int szamok[2][3] = {0};

A második példa azt mutatja meg hogy a mátrix lényegében csak elvi síkon 2D-s, valójában a memóriacímzésből adódóan csak is 1D-s lehet, mint a tömb:

Mátrix[Y][X] = P + Y * Szélesség + X

Ez lényegében a tömb képlete, csak most az indexet két számból állítjuk elő:

Index = Y * Szélesség + X

Ez úgy jön ki hogy ahhoz hogy az [Y][X]-dik elemhez eljussunk először végig kell menni Y soron, amelyben soronként annyi elem van amennyi a mátrix szélessége + még el kell jutni az X-dik elemhez is. A nullától indexelés kiküszöböli majd itt is az elcsúszásokat - hiszen ha Y=0, X=0 akkor Index = 0, vagyis Mátrix[0][0] = P.

A képletekből viszont az is következik hogyha a határokon átlépünk egy elemet máshogy is eléhetünk:

Mátrix[1][1] = Mátrix[0][4]

- de mint az a tömböknél már előkerült a túlindexelés akár hibákhoz is vezethet a védett mód miatt, a

Mátrix[2][3]

hivatkozás is összeomlasztja a programot. A fentebbi példa azért volt jó viszont mivel a képlettel számolt érték beleesik a mátrixnak, mint tömb, a határaiba - a másik viszont kiesik ebből:

  • ha tömb lenne 2*3=6 eleme lenne
  • tömbként az indexelése 0..5-ig történne
    • első példa: 1*3+1 = 0+4 = 4 <= 5
    • második példa: 2*3+3 = 9 > 5, így hiba fog történni

1b, A tér

A tér egy olyan mátrix amelynek az elemei tömbök. Mivel most már három koordináta kell az elem meghatározásához térbeli alakzatról beszélhetünk, méghozzá egy kockáról aminek 3x3x3 esetben 27 pontjában lesz szám (3 lap, laponként 9).

A tér még nem a legvége a dolgoknak de minden ami a mátrixra érvényes volt érvényes ide is kissé kiterjesztve.

 

Index = Z * Szélesség * Magasság + Y * Szélesség + X

Tér[Z][Y][X] = P + Index

Mind a túlindexelésre, mind a for ciklusokkal való használatára mind a konstansokra a fentebb megtett javaslatok és megállapítások érvényesek.

1c, Tetszőleges n dimenziós tömb

A tömbök egymásba ágyazása közel a végtelenségig lehetséges. Íme általános esetre néhány megoldás (legyenek a tengelyek x1, x2, ..., x{n-1}, xn).

Index =
XN * Méret(X{N-1}) * ... * Méret(X2) * Méret(X1) +
X{N-1} * Méret(X{N-2}) * ... * Méret(X2) * Méret(X1) +
... +
X2 * Méret(X1) + X1

Tömb[XN][X{N-1}]...[X2][X1] = P + Index

Mind a túlindexelésre, mind a for ciklusokkal való használatára mind a konstansokra a fentebb megtett javaslatok és megállapítások érvényesek.

2, Struktúra

Képzeljük el hogy szükségünk van egy evőeszköz készletre. Mikor kibontjuk a dobozt lesz benne pohár, villa, tányér, kanál - de egyik sem egyforma méretű, így a doboz különböző méretekre van osztva.

A programozás során is előfordulhat hogy ilyen gyűjtőre lenne szükségünk de a tömbök ezt nem egészen írják le jól - csak azonos változóból állhatnak. Ilyenkor jön be a struktúra.

Ahhoz hogy az evőeszköz készletet bedobozolhassák, szükség van egy tervrajzra. Ezen a tervrajzon rajta van hogy hol kell behajtani a kartont, mekkora az egyes eszközök mérete, mekkora lesz a doboz ha elkészült. Ez a struktúránál sincs máshogy - csak itt a tervrajz helyett a típusdefiníció lesz ami leírja annak az alakját.

2a, Típusdefiníció

Mikor struktúrát készítünk ténylegesen szükségünk lesz egy új típusra, aminek a leírása itt adható meg. Példaképp nézzük meg hogy hogyan lehet leírni egy személy adatait struktúrával:

typedef struct
{
  unsigned int eletkor;
  char nev[50];
  char lakcim[200];
  float kedvenc_szam;
  int szeme_szine, haja_szine, csaladi_allapot;
} TSzemely;

Mikor végeztünk a típusdefinícióval:

  1. még egy változónk sincs, csak egy "tervrajzunk"
  2. amit megadtunk nevet, az az új típusnak lesz a neve (TSzemely) - amivel majd változókat gyárthatunk

Tipp:

  • Az elnevezési konvenció alapján az alaptípus "T" (type) betűvel kezdődik, míg az alaptípusból készült mutató "P" (pointer) betűvel kezdődik. A példa ilyen átirata (ez után már egyből lesz mutató típusunk is):
    • ... } TSzemely, *PSzemely;
  • Bár ez nem jelent meg a példában, de érdemes olyan mezőneveket használni ami mutatja annak a típusát is, pl.:
    • iEletkor, szNev, szLakcim, fKedvencSzam, iSzemeSzine, iHajaSzine, iCsaladiAllapot

2b, A struktúrák felvétele

Ha már elkészítettük a TSzemely típusunkat, vegyünk fel változókat ilyen formán:

TSzemely Jani, Sanyi, Pista, 3b_osztaly[25];

Most felvettünk három személyhez változókat - Jani, Sanyi és Pista nevű változókba, illetve egy egész osztályhoz adatokat 25 fős létszámhoz igazítva (tömb által). Most már csak adatokkal kéne feltölteni ezeket.

Az adatok feltöltését megtehetjük előre megadott konstansként, illetve futásidőben [mindkét esetben az előre beharangozott "." operátor lesz a segítségünkre]:

TSzemely Jani =
         {
           .eletkor = 12,
           .nev = "Kovács János",
           .lakcim = "Kukutyin, Kossuth utca 5.",
           .kedvenc_szam = 3.14,
           .szeme_szine = 1,
           .haja_szine = 2,
           .csaladi_allapot = 0
         },
         Sanyi = {/*... mint az előbb ... */},
         Pista = {/*...*/},
         3b_osztaly[25] = {{/*...*/},
                           {/*...*/},
                           /*... még 21 elem leírása ...*/,
 
                           {/*...*/},
                           {/*...*/}};

A struktúrának a változói mezőknek hívjuk, ezeket a mezőket pedig elérhetjük a "." operátorral futásidőben is:

int osszeg = 0, i;
float atlag;

osszeg += Jani.eletkor;
osszeg += Sanyi.eletkor;
osszeg += Pista.eletkor;

for (i = 0; i < 25; i++)
  osszeg += 3b_osztaly[i].eletkor;

atlag = osszeg / 28;

Természetesen írni is lehet a mezőket, nem csak olvasni:

 3b_osztaly[24].eletkor = 13;

A struktúráknak később a fájloknál nagyon nagy haszna lesz, mivel ha a mezői között nincsen mutató - az összes mezejét egy sorban le lehet menteni, vagy kiolvasni a fájlból.

2c, A struktúramutató

Természetesen ha már új típusunk van a mutatóképző operátorral ebből is készíthetünk mutatót. Ez még a dinamikus struktúra alapú tömböknél fog előjönni.

TSzemely* Peti = &(3b_osztaly[4]);

Most azt mondjuk hogy Peti a 3b osztály 5. személye. Ilyenkor mivel ez egy mutató két módon érhetjük el a mezőket:

(*Peti).eletkor = 5;


Peti->eletkor = 5;

Erre való a nyíl operátor.

Érdekesség:
    A Peti változó most csak egy mutató - egy memóriacím, méghozzá a (3b_osztaly[4]) címe.
    Ebből kifolyólag ha olvassuk/írjuk azt a változót amire a Peti mutat (fentebbi példa mindkét esete) - akkor a 3b_osztaly[4] változót írjuk/olvassuk, mivel oda mutat.
    Ez a mutatók lényege, de erről majd később még lesz szó.

2d, Union

Az union definiálása szinte semmiben sem különbözik a struktúráétól, de sok különböző méretű típus esetén kevesebb memóriát fog foglalni mint a struktúra. Ez azért következhet be, mivel itt megszünteti azt a néhány byte hézagot ami felléphet.

Ez a hézag abból adódik, hogy a struktúra esetén a evőeszközös dobozt úgy készítem el hogy a tányérhoz méretezek minden rekeszt, elvégre akkor minden ami kisebb mint a tányér belefér a saját rekeszébe.

Az union ezzel szemben minden rekeszt a maga méretéhez készít el, ezzel csökkentve a kihasználatlan helyeket.

Definiálása:

typedef union
{
  char szoveg[20];
  int szam;
  float valos;
} TUnion;

2e, Flexibilis struktúra

A struktúra és az union kombinálásával megszülethet a flexibilis struktúra:

typedef union
{
  char
bA, bR, bG, bB;
} TARGBQuad;

typedef union
{
  unsigned long int
ullSzin;
} TARGBSzin;

typedef struct

{
  union
  {
     TARGBQuad aqErtekek;
     TARGBSzin asKombinalt;
  };
} TARGB;

1,

Itt az történt hogy készítettünk egy olyan struktúrát amivel ugyanazt a memóriaterületet más részekre oszthatjuk.

Ha az aqErtekek mezőt használjuk a bA, bR, bG, bB négy darab byte-ot jelöl, amik egy átlátszó szín összetevői (alpha, red, green, blue).

Ezek egyenként nem alkalmasak egy szín meghatározására, csak együtt - méghozzá úgy ha a 4 byte sorban így van mint a TARGBQuad-ban.

Mi van akkor ha nekünk ez a 4 byte egyben kéne (mint HTML-színkód pl.)? Ekkor az asKombinalt mezőt használjuk, ami ugyanarról a területről most a külön-külön lévő 4 byte-ot összeollózza, és egy számban engedi kiolvasni.

Fordítva is működik a dolog, ha az asKombinalt.ullSzin = 0xFF7FC724 színkódot beírjuk ebbe a 4 byteba, az aqErtekek mezőben kiolvashatjuk az egyes színösszetevők nagyságát, pl. aqKombinalt.bA == 255, aqKombinalt.bR = 127, ...

Mivel ugyanazt az adathalmazt többféleképpen módosíthatjuk, ezért lesz ez a struktúra flexibilis.

2,

Ha nem fontos hogy a mezők egymást fedjék akkor az union-ban lévő típusok lehetnek struktúrák is, pl. MSDN - ekkor nem fogják a mezők egymást fedni, vagy az egyik lesz egyszerre, vagy a másik.

Ez is flexibilis, de itt már nem olvashatjuk ki ugyanazt a területet másféleképpen.

3, Az enumeráció

Mi van akkor ha sorszámozott típusra lenne szükségünk, de nem számra? Ekkor jön képbe az enumeráció ami a struktúrához hasonlóan típusdefinícióval kezdődik.

Ennek pl. akkor van haszna ha a TSzemely struktúrában a haja_szine változót szám helyett, értelmes tartalommal szeretnéd felruházni. Íme hát néhány enum típus:

typedef enum
{
  ssBarna,
  ssKek,
  ssZold,
  ssFekete
} TSzemSzin;

typedef enum
{
  caKapcsolatban = 1,
  caEgyedulallo = 0,
} TCsaladiAllapot;

typedef enum
{
  hsEgyeb =
  hsBarna = 0,
  hsFekete,
  hsSzoke,
  hsVoros
} THajSzin;

A szám jellege továbbra is megmarad egy ilyen típusnak, de a szám most mégis index jellegű - és a név által tartalmat kap.

Az enum mezői is nullától számozódnak kivéve ha mást adsz meg:

  • ha nincs megadva semmi, akkor az első érték a 0, a következő 1, és így tovább. (1)
  • ha mind megvan adva, az a mérvadó - teljesen random sorrendben is lehetnek a számok, nem csak növekvőben (2)
  • ha megvan adva egy érték, de más nincs, akkor az az lesz aminek meg lett adva, és a többit ahhoz képest számozza. (3)

Példa:

THajSzin haj1 = hsBarna;

haj1++; /* most már hsFekete */
haj1 += 2; /* most már hsVoros */

Tippek:

  • Az elnevezési konvenció alapján az alaptípus "T" (type) betűvel kezdődik, míg az alaptípusból készült mutató "P" (pointer) betűvel kezdődik.
  • Az enum mezői általában a típus rövidítésével kezdődnek - így egyértelmű hogy melyik enumból való az érték, valamint az automata kiegészítésnél elég begépelni hogy "hs" és máris kiadja a gép az össze hajszín variációt.

4, Álnév (alias)

Az álnév során egy már létező típusnak adunk új nevet pl.:

typedef unsigned char byte;

Mostantól az unsigned char típus byte néven is ismeretes:

byte bSzam = 127;

Az alias segítségével készíthetőek mutatók is, tömbök is mint új típus:

typedef byte* PByte;


typedef char TSzoveg[255];

Sőt akár még függvényekhez is készíthetünk velük mutatót (későbbiekben):

typedef float (*TFuggveny)(float);

Habár ez most csúnyának tűnhet, lényegében egy sima függvénydeklaráció ahol a függvény nevét (*fv_név) alakban írtuk be, és a bekért adatoknak nincs neve. A függvényekkel foglalkozó témakör sok dologra rávilágíthat majd még ezzel kapcsolatban.

5, Halmazok

Ez is egy könnyed témakör mint a tömb vagy a struktúra. Megismeréséhez szükséges a bitenkénti logikai operátorok megértése.

A C/C++ halmaz esetén annyi elemünk lehet egy halmazban, ahány bites a gép. Ez abból adódik hogy minden egyes bitet, úgy fogunk kezelni egy pl. 32-bites számban, mint egy-egy logikai típust, melyek megadják hogy az adott elem benne van-e a halmazban.

1,

Tehát először is készítsünk el egy enumot, a halmazelemek megadásával - vonatkozzon pl. egy betűtípusra stílusára:

typedef enum
{
  bsFelkover = 1,
  bsDolt = 2,
  bsAlahuzott = 4,
  bsAthuzott = 8,
  bsAlsoIndex = 16,
  bsFelsoIndex = 32,
  bsKiskapitalis = 64,
  bsNagykapitalis = 128
}
TBetuStilus;

Bizonyára feltűnt hogy a kettő hatványait kellett használni a számérték megadásakor. Ez azért van mert így a számértékek bináris rendszerben mindig csak egy helyiértéken lesznek 1-esek a többin mind 0.

pl.:

bsFelkover  = 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0001
bsDolt      = 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0010
bsAlahuzott = 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0100
bsAthuzott  = 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 1000
bsAlsoIndex = 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0001 0000

és így tovább, és így tovább...

Mivel kettes számrendszerben jól ábrázolható az alak, ezért szokás 16-os számrendszerben felírni a számokat (2^16 után már nagyon használhatatlan a decimális alak):

typedef enum
{
  bsFelkover      = 0x00000001,
  bsDolt          = 0x00000002,
  bsAlahuzott     = 0x00000004,
  bsAthuzott      = 0x00000008,
  bsAlsoIndex     = 0x00000010,
  bsFelsoIndex    = 0x00000020,
  bsKiskapitalis  = 0x00000040,
  bsNagykapitalis = 0x00000080
}
TBetuStilus;

Így már egész jól átlátható.

2,

A halmaz típusa nem lesz más mint egy alias:

typedef unsigned long int TBetuStilusok;

Megjegyzés: Érdemes megfigyelni itt is az elnevési trendet.

3,

A halmazunk készen áll a bevetésre:

  • lesz egy egész előjel nélküli számunk

  • bitenkénti logikai műveletekkel ki/betehetünk elemeket

  • feltételes utasításban és művelettel ellenőrizhetjük hogy azok benne vannak-e a halmazban

5a, Halmazelemekkel kapcsolatos műveletek

Vegyük azt hogy a halmazunk ismeretlen binárisan, de szeretnénk beletenni egy elemet, pl. a bsFelkovert:

A megfelelő művelet az lesz ami, a többi részen megkíméli az eredeti állapotot - de a bsFelkover szükséges bitjét átírja. Ez nem lesz más mint a vagy művelet (és itt most fontos hogy a sima "|" jelet használjuk - a "||" logikai típusokhoz van fenntartva).

bsHalmaz    = ???? ???? ???? ???? ???? ???? ???? ???? ???? ???? ???? ???? ???? ???? ???? ????
bsFelkover  = 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0001


eredmény   = ???? ???? ???? ???? ???? ???? ???? ???? ???? ???? ???? ???? ???? ???? ???? ???1

A vagy művelet ahol alul egy darab egyes van ott átírja egyre a bsHalmazt (?|1->1), a többi helyen pedig hogyha két nulla volt - nulla marad (0|0->0); ha az eredetiben egyes volt - egyes marad  (1|0->1).

bsHalmaz = bsHalmaz | bsFelkover;

Most nézzük meg azt hogyan lehet kivenni egy elemet a halmazból pl. a bsDoltet:

Ehhez két művelet kell alternálás, és az és:

bsHalmaz    = ???? ???? ???? ???? ???? ???? ???? ???? ???? ???? ???? ???? ???? ???? ???? ????
~bsDolt     = 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1101


eredmény   = ???? ???? ???? ???? ???? ???? ???? ???? ???? ???? ???? ???? ???? ???? ???? ???1

Ahol egyes volt, ott egyes marad az eredeti (1&1->1), ahol nulla volt az eredeti ott nulla marad (0&1->1), ahol pedig a ~bsDolt lesz nulla ott az eredetiből el kell hogy tűnjön az egyes ha volt egyáltalán (?&0->0, a második feltétel nem teljesül alapból).

bsHalmaz = bsHalmaz & ~bsDolt;

Most pedig nézzük meg hogyan lehet leellenőrizni hogy egy adott elem benne van-e a halmazban:

Most az és művelet lesz a segítségünkre:

bsHalmaz    = ???? ???? ???? ???? ???? ???? ???? ???? ???? ???? ???? ???? ???? ???? ???? ????
bsAlahuzott = 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0100


eredmény   = 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0?00

Mindenhol ahol nulla van a bsAlahuzottban, ott úgyis nulla lesz (?&0->0), de ahol egyes volt benne ott még megvan a lehetősége hogy más szám is legyen (?&1->?). Ha ott a kérdésésen helyen egyes volt (1&1->1) az eredmény a bsAlahuzott lesz önmaga, de ha ott nulla volt (0&1->0) akkor az eredmény is csupa nulla -> számszerűen is nulla lesz:

if (bsHalmaz & bsAlahuzott != 0) { ... };


if (bsHalmaz & bsAlahuzott == bsAlahuzott) { ... };

Mint arról szó esett az alaptípusoknál minden ami nem nulla az igaz, tehát az if-be ennyit kell írni:

if (bsHalmaz & bsAlahuzott) { ... };

5b, Halmazok közötti műveletek

Lényegében mindent elvégeztünk egy-egy elemre, de az előző logikából kiindulva ezek kombinálhatóak is:

bsHalmaz = bsFelkover | bsAlahuzott | bsDolt & ~bsKiskapitalis;

Ehhez semmi mást nem kell, csak a logikai tábláknál, több 1/0 lesz attól függően hogy éppen mi kell nekünk - az eredmény ugyanaz lesz - ezúttal több elemmel egyszerre.

Egy ilyen sort csak ki kell olvasni:

"bsHalmaz egyenlő bsFélkövér, vagy bsAláhúzott, vagy bsDőlt, de nem lehet bsKiskapitális"

Megjegyzés: programozási szempontból az "és", és a "de" kulcsszó között nincs különbség.

Az előzőekből kiindulva már könnyen belátható hogy:

  1. Két halmaz uniója: H = H1 | H2
  2. Két halmaz metszete: H = H1 & H2
  3. Két halmaz különbsége: H = H1 & ~H2
  4. Két halmaz egyenlősége: if (H1 = H2) { ... }
  5. Két halmaz részhalmazúsága: if (H1 & H2 == H2) {...}
    Megjegyzés: Itt kell hogy egyenlő H2-vel: ha nem részhalmaza, de néhány elem közös akkor az eredmény a közös elemeket fogja tartalmazni és nem a nullát.

hiszen ha az elemekre összevonhatóak a műveletek, azaz több elemmel egyszerre lehet elemműveletet végezni, akkor lényegében a halmaz elemek gyűjteményeként, egy az egyben vonható ki/adható össze, stb.

Innen kiindulva viszont az enumba úgymond kész részhalmazokat is fel lehet venni:

typedef enum
{
  bsFelkover      = 0x00000001,
  bsDolt          = 0x00000002,
  bsAlahuzott     = 0x00000004,
  bsAthuzott      = 0x00000008,
  bsAlsoIndex     = 0x00000010,
  bsFelsoIndex    = 0x00000020,
  bsKiskapitalis  = 0x00000040,
  bsNagykapitalis = 0x00000080
,
  bsKitevo        = 0x000000A0,
//bsNagykapitalis | bsFelsoIndex
 
bsKisatirozott  = 0x0000000C  //bsAlahuzott | bsAthuzott
}
TBetuStilus;

És így használni őket:

bsHalmaz = bsHalmaz | bsKitevo | bsDolt & ~bsKisatirozott;