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

Czirkos Zoltán · 2023.10.13.

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. Próba ZH

A feladat segít felmérni, hogy állsz a tanulással. A szövege csak a többi megoldással együtt jelenik meg. Óra után pedig rögzítsd a pontszámod az admin portálon – a dolgozat nálad marad.

Feladat

Írj függvényt, amely paraméterként vesz át egy egészekből álló tömböt! A függvény adja vissza címükkel átvett változókban a tömb legkisebb és legnagyobb elemének indexét! Ha több egyforma érték a legkisebb/legnagyobb, akkor ezek közül bármelyik indexét visszaadhatja.

Mutass példát a függvény hívására! Definiálj egy ötelemű tömböt, hívd meg a függvényt, majd a kapott indexeket felhasználva írd ki a legkisebb és legnagyobb elem értékét!

A feladat megoldására 7 perc áll rendelkezésre.

Mintamegoldás és pontozási útmutató
void minmax(int *tomb, int n, int *pmin, int *pmax) {
    int min = 0, max = 0;
    for (int i = 1; i < n; i++) {
        if (tomb[i] < tomb[min]) min = i;
        if (tomb[i] > tomb[max]) max = i;
    }
    *pmin = min;
    *pmax = max;
}

int tomb[5] = { 89, 76, 34, 19, 47 };
int min, max;
minmax(tomb, 5, &min, &max);
printf("Legkisebb: %d, legnagyobb: %d", tomb[min], tomb[max]);

Mindegyik tétel 1 pontos:

  • a) Példa: tömb létrehozása (legyen méret megadva vagy inicializálva)
  • b) eredmény változók, min és max; int típusúak, nem int*!
  • c) tömb (& nélkül!), tömb mérete és min/max (& kell) átadása
  • d) eredmény kiírásánál a kapott indexek helyes felhasználása
  • e) Függvény paraméterei: int* és int a tömbhöz, int* és int* az eredményekhez
  • f) lokális min, max változók, amik nem pointerek (!), VAGY mindenhol *pmin, *pmax használata
  • g) szélsőérték keresése: változók kezdetben nullára állítása; ez a függvény feladata, nem a hívóé!
  • h) szélsőérték keresése: ciklus vizsgálja a többi elemet
  • i) min, max eredmények végleges beírása a kért helyre (ha mindenhol *pmin, *pmax volt, akkor automatikusan teljesül)
  • j) Összkép: helyes indentálás, nincs *(tomb+i) alakú kifejezés, szerepre utaló változónevek

Ha hely (index) helyett a tömbbeli értéket adja a függvény a hívójának, akkor a g-h-i pontok nem járnak.

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

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

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

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

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

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!