Gyakorlat, 5. hét: karakterek, számrendszerek

Czirkos Zoltán · 2017.08.14.

Karakterek kezelése, a karakterkódolás fogalma. Számrendszerek, számrendszerek közötti átalakítások.

Felkészülés a gyakorlatra:

1. Nagybetű, kisbetű, Caesar-kódolás

Írjunk programot, amelyik beolvas egy szöveget, és csupa kisbetűsítve (vagy csupa nagybetűsítve) írja ki azt! Az összes többi karakter maradjon változatlan a szövegben! Oldjuk meg a feladatot úgy is, hogy saját magunk kezeljük a karakterkódot, és úgy is, hogy az erre való beépített függvényt használjuk!

Megoldás

A betűk is csak számok a gép számára. Így aztán két karakter (karakterkód!) kivonása is értelmes művelet. Az általában használt ASCII kódtáblában egymás mellett vannak ábécé sorrendben a nagybetűk, és egy másik tartományban egymás mellett a kisbetűk. Emiatt a 'C'-'A' kifejezés 2-t ad, mivel a C és az A betű között 2 a távolság. Ez az a gondolat, ami a megoldáshoz vezet: ha egy nagybetű kódjából levonjuk az 'A' kódját, megkapjuk, hogy hányadik betű az ábécében; utána hozzáadva az 'a' kódját, megkapjuk az annyiadik kisbetűt, és fordítva. Másképpp gondolkodva: az 'a'-'A' kifejezés a két tartomány távolságát adja meg, ezt kell hozzáadni vagy kivonni egy kódhoz, hogy a nagybetűt kisbetűvé alakítsuk, és fordítva. Fontos, hogy ehhez nem kell tudnunk a karakterkódok konkrét számértékeit!

Kisbetűből nagybetű:

#include <stdio.h>

int main(void) {
    char cbe, cki;
    while (scanf("%c", &cbe) == 1) {
        if (cbe >= 'a' && cbe <= 'z') {
            cki = cbe - 'a' + 'A';
        } else {
            cki = cbe;
        }
        printf("%c", cki);
    }

    return 0;
}

A program fájl vége jelig olvas, azaz F6Enter-rel lehet kilépni belőle (Windows), vagy Ctrl+D-vel (Linux). Ugyanez a ctype.h toupper() függvényét használva:

#include <stdio.h>
#include <ctype.h>

int main(void) {
    char cbe;
    while (scanf("%c", &cbe) == 1) {
        printf("%c", toupper(cbe));
    }

    return 0;
}

Írjuk át a programot úgy, hogy a Caesar-féle titkosírást használva alakítsa át a szöveget! Ebben minden betűt az ábécé következő betűjére kell cserélni, pl. A→B, B→C... Z→A. A rómaiak csak nagybetűket használtak, de megírhatjuk úgy is a programot, hogy ismerje a kisbetűket és a nagybetűket is.

Megoldás

A program váza ugyanaz mint az előbb: ciklus (összes karakter), azon belül esetszétválasztás (a karakter átalakítása) és kiírás.

Az alábbi megoldás hierarchikusan választja szét a karaktereket: a külső if()-ek a csoportot nézik (kisbetű, nagybetű, egyéb), a belső if()-ek már a Caesar-kódolást.

#include <stdio.h>

int main(void) {
    char cbe, cki;
    while (scanf("%c", &cbe) == 1) {
        if (cbe >= 'a' && cbe <= 'z') {         /* kisbetű? */
            if (cbe == 'z')
                cki = 'a';      /* z->a */
            else
                cki = cbe+1;    /* a->b */
        }
        else if (cbe >= 'A' && cbe <= 'Z') {    /* nagybetű? */
            if (cbe == 'Z')
                cki = 'A';      /* Z->A */
            else
                cki = cbe+1;    /* A->B */
        }
        else {                                  /* egyéb. */
            cki = cbe;
        }
        printf("%c", cki);
    }

    return 0;
}

Az alábbi program pontosan ugyanezt csinálja, de nincs benne a hierarchikus esetszétválasztás. A +1-es képlet a kisbetűknél és a nagybetűknél is működik, ezért az is egyesítve lett, és a cki változó használata helyett mindenhova bekerült a printf(). A program így sokkal tömörebb, de nehezebben olvasható:

#include <stdio.h>

int main(void) {
    char cbe;
    while (scanf("%c", &cbe) == 1) {
        if ((cbe >= 'a' && cbe < 'z') || (cbe >= 'A' && cbe < 'Z'))
            printf("%c", cbe+1);
        else if (cbe == 'z')
            printf("a");
        else if (cbe == 'Z')
            printf("A");
        else
            printf("%c", cbe);
    }

    return 0;
}

2. Operátorok és kiértékelés

Adjuk meg az alábbi kifejezésekhez tartozó kifejezésfát, figyelembe véve az operátorok precedenciáját!

  • 6+2*3
  • 2*6-5/3
  • a=b+c
  • t[i+2]*3
  • 5*-6 és 5-*6

Az alábbi rajzok közül az egyik az x=(double)a/b kifejezésfáját ábrázolja. Melyik az? Adjuk meg a másik fa által ábrázolt kifejezést!

Megoldás

x=(double)a/b

A megadott kifejezést az első kifejezésfa ábrázolja. A másik kifejezés pedig: x = (double) (a/b). Zárójelbe kell tenni az a/b-t, hogy a típuskonverzió az osztás eredményére vonatkozzon. Zárójel nélkül az a változó értékére vonatkozik, mivel erősebb a konverziós operátor precedenciája, mint az osztás operátoré.

3. Számrendszerek

hexbindec
000000
100011
200102
E111014
F111115

Váltsuk át papíron a következő 2-es számrendszerbeli (bináris) számokat 10-esbe (decimális): 1011, 1101, 10000, 10101! Váltsuk át papíron a következő decimális számokat binárisba: 13, 27, 35.

Váltsuk át a következő számokat hexadecimálisból binárisba: 0x1F, 0xFCE2, 0xABBA, 0xC0FFEE! Váltsuk át ezeket binárisból hexadecimálisba: 11111111, 1011100, 101, 10101! Ezeket a feladatokat úgy végezzük el, hogy közben nem használjuk a tízes számrendszert!

Tipp

24=16, ezért minden hexadecimális (16-os számrendszerbeli) számjegynek pontosan 4 darab bináris számjegy felel meg, ahogyan az jobb oldalt is látható. Emiatt pl. a bináris 101011 hexadecimális értéke 0x2B, mivel alulról 4-es csoportokban 10'1011, és 10=0x2, illetve 1011=0xB.

Végezzük el az alábbi műveleteket kettes számrendszerben, tízesbe átváltás nélkül!

 1001
+ 101
─────
 1111
+1000
─────
 1111
+   1
─────

4. Beolvasás adott számrendszerben

Olvassunk be egy felhasználó által megadott számot, egy felhasználó által megadott számrendszerben!

Először elég, ha tízes számrendszerig működik a program, csak utána írjuk át úgy, hogy működjön nagyobb alap esetén is!

Hányas számrendszerben fogsz írni?
16
Ird be a szamot!
fce2
A beolvasott szám 10-es számrendszerben: 64738

Megoldás

A megoldáshoz nem használhatjuk közvetlenül a scanf()-et, mert az csak a tízes (%d), a tizenhatos (%x) és a nyolcas (%o) számrendszert ismeri. Egyesével kell beolvasnunk a számjegyeket, és a helyiértékek kiszámításával meghatároznunk a számot.

Az egyszerű számításhoz felhasználhatjuk a Horner-elrendezést. Vegyük példának ehhez 10-es számrendszerben a 234-et! Ezt az egyes számjegyekből 2×102+3×101+4×100 formában határozhatjuk meg. Ami pedig ugyanaz, mint ((2×10)+3)×10+4, azaz (((0×10+2)×10)+3)×10+4, amiből már látszik, hogyan kell dolgoznunk: mindig a meglévő részeredményt megszoroznunk tízzel, és hozzáadni az új számjegy értékét. Ha eddig a 23-at láttuk, és megkapjuk a 4-est, akkor a szóban forgó lépés 23×10+4 = 230+4 lesz. Ezt kell folytatni egészen addig, amíg határoló karaktert nem kapunk, persze a tízzel szorzás helyett az adott számrendszer alapját tekintve.

A számjegy beolvasásánál figyelembe kell venni, hogy bár számjegyről beszélünk, tízes számrendszer fölött ez lehet betű is. Ezért a beolvasott karaktert meg kell vizsgálni, számjegyről van-e szó (0...9) vagy betűről (a...z). Ha számjegyről, akkor a '0'-s számjegy karakterkódját kell kivonni belőle, hogy számértéket kapjunk, amúgy pedig az 'a' betű karakterkódját, és hozzáadni 10-et, mert A=10, B=11 stb. Itt kapóra jönnek a ctype.h függvényei: isdigit(c) = számjegy-e?, isalpha(c) = betű-e?, toupper(c) = a karakter nagybetűként (ha szépen szeretnénk csinálni).

#include <stdio.h>
#include <ctype.h>

int main(void) {
    /* alap */
    int alap;
    printf("Hányas számrendszerben fogsz írni?\n");
    scanf("%d", &alap);

    /* szám beolvasása */
    printf("Ird be a szamot!\n");
    int szam = 0;
    char c;
    /* az alap beírása után nyomott enter eldobása.
     * különben a ciklus scanf %c-je azt is visszaadná! */
    scanf(" ");
    while (scanf("%c", &c) == 1 && !isspace(c)) {
        /* karakter (lehet betű és számjegy is) számértékké alakítása */
        int ertek;
        if (isdigit(c))
            ertek = c - '0';
        if (isalpha(c))
            ertek = toupper(c) - 'A' + 10;
        /* Horner-elrendezés */
        szam = szam*alap + ertek;
    }

    printf("A beolvasott szám 10-es számrendszerben: %d\n", szam);

    return 0;
}

5. Kiírás adott számrendszerben

Írjunk programot, amelyik adott számot ír ki adott számrendszerben!

Oldjuk meg a feladatot úgy is, hogy tömböt használunk, és úgy is, hogy nem!

Megoldás

A számjegyeket meghatározni könnyű: ha a számot modulózzuk a számrendszer alapjával (tehát elosztjuk vele, és a maradékot vesszük, a % operátorral), akkor megkapjuk a legalsó számjegyet. Ha pedig elosztjuk vele, akkor az egész szám csúszik az egyesek felé – hogy után a következő számjegyet kapjuk meg a modulózás által. Ezt kell folytatni egészen addig, amíg el nem érünk a nulláig, mert a sok osztás után a szám előbb-utóbb nulla lesz:

Ez még hibás
#include <stdio.h>

int main(void) {
    int alap, szam;
    
    printf("Melyik számrendszerben?\n");
    scanf("%d", &alap);
    printf("Melyik szám (tízesben)?\n");
    scanf("%d", &szam);

    while (szam > 0) {
        printf("%c", (szam%alap) + '0');   // rossz sorrend :(
        szam /= alap;
    }
    return 0;
}

A gond ezzel „csak” az, hogy így előbb az egyeseket kapjuk meg, és csak utána az egyre nagyobb helyiértékeket... Miközben először a legnagyobb helyiérték kellene, és csak legutoljára az egyesekhez tartozó számjegy:

Melyik számrendszerben?
10
Melyik szám (tízesben)?
123
A szám az adott számrendszerben:
321

A legegyszerűbb a sorrend megfordítására, ha egy tömbben eltároljuk a kapott karaktereket, aztán a végén kiírjuk őket fordított sorrendben. Karaktereket kell eltárolni, tehát karakterek tömbjére van szükségünk:

Ez már jó,
a tömbös megoldás
#include <stdio.h>

int main(void) {
    int alap, szam;
    
    printf("Melyik számrendszerben?\n");
    scanf("%d", &alap);
    printf("Melyik szám (tízesben)?\n");
    scanf("%d", &szam);

    /* számjegyek előállítása, eltárolás */
    char szamjegyek[32];
    int db = 0;
    while (szam > 0) {
        szamjegyek[db++] = szam%alap + '0';
        szam /= alap;
    }

    /* kiírás fordított sorrendben */
    int i;
    for (i = db-1; i >= 0; --i) {
        printf("%c", szamjegyek[i]);
    }
    
    return 0;
}

Tömb nélkül kicsit másképp kell gondolkodunk. A legrövidebb megoldás azon alapul, hogy megkeressük a számrendszer legkisebb hatványát, amelyik már nagyobb az átalakítandó számnál, és attól indulva jövünk visszafelé. Pl. ha a kiírandó szám 1234 (tízes számrendszerben), akkor 1000-rel, 100-zal, 10-zel és 1-gyel osztva az eredeti számot, kapunk olyan számokat, amelyben a legalsó helyiértéken van a kiírandó számjegy (1, 12, 123, 1234).

#include <stdio.h>

int main(void) {
    int alap, szam;
    printf("Melyik számrendszerben?\n");
    scanf("%d", &alap);
    printf("Melyik szám (tízesben)?\n");
    scanf("%d", &szam);

    /* következő nagyobb hatvány megkeresése */
    int kitevo = 1;
    while (kitevo <= szam)
        kitevo *= alap;
    
    /* eggyel visszalépünk, és 1-ig jönnek az osztók */
    kitevo /= alap;
    while (kitevo > 0) {
        printf("%d", (szam/kitevo) % alap);
        kitevo /= alap;
    }
    
    return 0;
}

A fenti megoldás hiányossága még, hogy 0-ra nem ír ki semmit, mivel az alsó ciklus egyszer sem fut le.

A tömb nélküli megoldás előnye, hogy bármekkora számra tud működni. A tömbösnél meg kellett becsülnünk a tömb méretét (a mintamegoldás abból indult ki, hogy a kettes számrendszerhez kell a legtöbb számjegy, és hogy az int általában 32 bites).

Módosítsuk úgy a programot, hogy 10-nél nagyobb alapú számrendszerben is (pl. 16-osban) működjön! A 10-et, és annál nagyobb számjegyeket ilyenkor betűkkel szokás jelölni. Pl. 16-osban a 0…15 számjegyek: 012…89ABCDEF.

Megoldás

Ehhez csak a kiírást kell módosítani, figyelembe véve, hogy 10-nél nagyobb-e a kapott számjegy. A vonatkozó programrész, kiegészítve az esetlegesen 0 értékű szám kiírásával is:

if (szam == 0)
    printf("0");
else {
    /* eggyel visszalépünk, és 1-ig jönnek az osztók */
    kitevo /= alap;
    while (kitevo > 0) {
        int ertek = (szam/kitevo) % alap;
        printf("%c", ertek >= 10 ? ertek-10+'A' : ertek+'0');
        kitevo /= alap;
    }
}

6. További feladatok

Igazságtáblák

Készítsünk programokat, amelyek 2 bitre vagy több bitre készítik el a logikai függvények igazságtábláit!

Bitsorozat kivágása

Oldjuk meg a feladatgyűjtemény bitsorozat kivágása című feladatát!