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

Czirkos Zoltán · 2018.08.22.

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. Második kis ZH

Ezen az órán van a második kis ZH.

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
Megoldás
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. Nagybetű, kisbetű

Í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! A programba karakterkódokat írni barbár dolog.

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;
}

4. 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
─────

5. 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

Megoldási terv, ötletek

  • Beolvasunk egy számjegyet (karaktert), és kivonjuk belőle a '0' karakterkódját. Így megkapjuk az értékét.
  • Hogy lesz egy számjegyből sok? Pl. 123 esetén, ha a 12-t már beolvastuk, és jön még egy 3-as, akkor a 12 igazából a 120-at jelentette (megszorozzuk 10-zel), és ahhoz adjuk a 3-at. Ha 123 esetén 4-es jön, akkor igazából 1230 volt, és ahhoz adjuk a 4-et.
  • Vagyis mindig az eddigi, szorozva 10-zel (a számrendszer alapjával), plusz az új számjegy.

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;
}

6. 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

Ötletek a megoldáshoz

  • Ha a számot elosztjuk a számrendszer alapjával, és a maradékot tekintjük, az a legutolsó számjegy. Például 1234 % 10 = 4, a szám 4-esre végződik.
  • Ha a hányadost, az a többi számjegyet adja meg: 1234 / 10 = 123.
  • Ezt folytatva megkapjuk az összes számjegyet, csak meg kell a sorrendet fordítani majd.

Első verzió

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

Második verzió

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 */
    for (int 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;
    }
}

7. 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!