Kihagyás

11. gyakorlat

8. ZH

A gyakorlat elején 45 percben kerül megírásra a 8. zh. A feladat témái:

  • algoritmus elkészítése
  • teljes program megírása, bemenet/kimenet kezeléssel, adatfeldolgozással
  • tömbök kezelése

Felsorolás típus (enum)

Az enum-hoz tartozó feladatok megoldása előtt érdemes lehet tanulmányozni az előadásjegyzet ide vonatkozó részét.

Feladat (f0150)

Vizsgáld meg az enum.c programot!

  • Javítsd ki a kovetkezo() függvény módosításával úgy, hogy az első kiírás (az év első tíz napja) helyes legyen!
  • És most javítsd ki a második ciklust (a hét napjai), hogy ne legyen végtelen ciklus! Ha kell használhatsz másfajta ismétlést is!
  • Mi történik, ha Hetfo = 1 -ként adod meg az enum het típus első elemét?
  • Mi történik, ha Szombat = 10 -ként adod meg a hatodik elemet?
  • Tudod-e az enum több elemének is ugyanazt az értéket adni? Mi történik, ha mindegyiknek ugyanazt adod?

enum.c:

 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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/*
 * Szegedi Tudományegyetem
 * Informatikai Tanszékcsoport
 * Szoftverfejlesztés Tanszék
 *
 * Programozás Alapjai
 */

#include <stdio.h>
#include <stdlib.h>

enum het { Hetfo, Kedd, Szerda, Csutortok, Pentek, Szombat, Vasarnap };

enum het kovetkezo(enum het nap) {
    return nap + 1;
}

char * toString(enum het nap) {
    switch(nap) {
      case Hetfo:     return "Hetfo";
      case Kedd:      return "Kedd";
      case Szerda:    return "Szerda";
      case Csutortok: return "Csutortok";
      case Pentek:    return "Pentek";
      case Szombat:   return "Szombat";
      case Vasarnap:  return "Vasarnap";
    }
    return NULL;
}

int main() {
    enum het nap;
    int i;
    printf("Egy vasárnap kezdődő év első 10 napja:\n");
    for (i = 1, nap = Vasarnap; i <= 10; ++i, nap = kovetkezo(nap)) {
        printf("  Jan. %d.: %s\n", i, toString(nap));
    }
    printf("A hét napjai:\n");
    for (nap = Hetfo; nap <= Vasarnap; nap = kovetkezo(nap)) {
        printf("  #%d: %s\n", nap, toString(nap));
    }
    return 0;
}

Lehetséges válaszok (f0150)
  • A kovetkezo függvényben kell egy feltétel:
    1
        return (nap == Vasárnap) ? Hetfo : nap + 1;
    
  • Egy do while ciklus megfelelő lehet:
    1
    2
    3
    4
    5
    nap = Hetfo;
    do {
        printf("  #%d: %s\n", nap, toString(nap));
        nap = kovetkezo(nap);
    } while (nap != Hetfo);
    
  • A 40. sorban lévő kiírás eredményeként a kimeneten a napok nem 0-tól 6-ig, hanem 1-től 7-ig lesznek számozva. Más eltérés nem lesz.
  • Január 6-ig rendben lesznek a napok, de utána 7-10-ig nem lesz hozzájuk nap rendelve. A hét napjainak felsorolásánál 11 vagy 12 elem lesz felsorolva (attól függően, hogy Hetfo = 1, vagy Hetfo, szerepel az enum definíciójában), de a 6-9 vagy 5-9 sorszámú napoknak nem lesz neve.
  • Igen, elméletileg lehet ugyanolyan értékű bármely kettő, sőt, akár az összes elem is. Ekkor viszont a program nem fog lefordulni, mert a toString függvényben lévő switch-nek ebben az esetben több azonos értékű case ága is lesz, ez pedig fordítási hibát okoz.

Feladat (f0154)

Vizsgáld meg az enum.c programot!

  • Módosítsd úgy, hogy az enum kulcsszó csak egyszer szerepeljen benne, de a program lényegében ne változzon meg! A megvalósításhoz nem használhatsz #define-t!

enum.c:

 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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/*
 * Szegedi Tudományegyetem
 * Informatikai Tanszékcsoport
 * Szoftverfejlesztés Tanszék
 *
 * Programozás Alapjai
 */

#include <stdio.h>
#include <stdlib.h>

enum het { Hetfo, Kedd, Szerda, Csutortok, Pentek, Szombat, Vasarnap };

enum het kovetkezo(enum het nap) {
    return nap + 1;
}

char * toString(enum het nap) {
    switch(nap) {
      case Hetfo:     return "Hetfo";
      case Kedd:      return "Kedd";
      case Szerda:    return "Szerda";
      case Csutortok: return "Csutortok";
      case Pentek:    return "Pentek";
      case Szombat:   return "Szombat";
      case Vasarnap:  return "Vasarnap";
    }
    return NULL;
}

int main() {
    enum het nap;
    int i;
    printf("Egy vasárnap kezdődő év első 10 napja:\n");
    for (i = 1, nap = Vasarnap; i <= 10; ++i, nap = kovetkezo(nap)) {
        printf("  Jan. %d.: %s\n", i, toString(nap));
    }
    printf("A hét napjai:\n");
    for (nap = Hetfo; nap <= Vasarnap; nap = kovetkezo(nap)) {
        printf("  #%d: %s\n", nap, toString(nap));
    }
    return 0;
}

Segítség (f0154)

Mire jó a typedef kulcsszó?

Lehetséges megoldás (m0154.c)
 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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
/*
 * Szegedi Tudományegyetem
 * Informatikai Tanszékcsoport
 * Szoftverfejlesztés Tanszék
 *
 * Programozás Alapjai feladat megoldása
 *
 * Gergely Tamás, 2008. őszi félév.
 *
 * fordítás:
 *    gcc -o m0154 m0154.c
 *
 * futtatás:
 *    ./m0154
 */

#include <stdio.h>
#include <stdlib.h>

typedef enum { Hetfo, Kedd, Szerda, Csutortok, Pentek, Szombat, Vasarnap } het_t;

het_t kovetkezo(het_t nap) {
    return nap + 1;
}

char * toString(het_t nap) {
    switch (nap) {
      case Hetfo:     return "Hetfo";
      case Kedd:      return "Kedd";
      case Szerda:    return "Szerda";
      case Csutortok: return "Csutortok";
      case Pentek:    return "Pentek";
      case Szombat:   return "Szombat";
      case Vasarnap:  return "Vasarnap";
    }
    return NULL;
}

int main() {
    het_t nap;
    int i;
    printf("Egy vasárnap kezdődő év első 10 napja:\n");
    for (i = 1, nap = Vasarnap; i <= 10; ++i, nap = kovetkezo(nap)) {
        printf("  Jan. %d.: %s\n", i, toString(nap));
    }
    printf("A hét napjai:\n");
    for (nap = Hetfo; nap <= Vasarnap; nap = kovetkezo(nap)) {
        printf("  #%d: %s\n", nap, toString(nap));
    }
    return 0;
}

Preprocesszálás

A preprocesszálásról itt olvashatsz bővebben (érdemes lehet a feladatok előtt átfutni).

Feladat (f0151)

Vizsgáld meg a preproc.c programot!

  • Hol jelez hibát a fordító?
  • Mi lesz, ha elhagyjuk a main függvény elől az int-et?
  • Vizsgáld meg a preprocesszált állományokat! Mit tapasztalsz? Miért működik az egyik, és miért nem a másik verzió? (A preprocesszált fájlokat a gcc -E kapcsolójával tudod előállítani, ami alaphelyzetben a standard kimenetre ír. Érdemes tehát vagy a -o fordítási opcióval a kimeneti fájlt megadni, vagy a fordító kimenetét a shell-ben egy fájlba irányítani. A preprocesszált fájlok szokásos kiterjesztése a .i.)

preproc.c:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
/*
 * Szegedi Tudományegyetem
 * Informatikai Tanszékcsoport
 * Szoftverfejlesztés Tanszék
 *
 * Programozás Alapjai
 */

#define int 100.0

int main() {
    float f = int;
    printf("%f\n", f);
    return 0;
}

Lehetséges válaszok (f0151)
  • A 11. sorban a main előtt.
  • Legfeljebb egy warningot kapunk, de a program rendben lefordul és hiba nélkül fut (kiírja a 100 valós értéket 6 tizedesjegy pontossággal).
  • Az int-ek le lettek cserélve 100.0-ra, ami a main előtt (ahová típus kellene) hibát okoz. Mivel a csere csak lexikális (nem az int típust cseréli le, csupán az int karaktersorozat előfordulásait még fordítás előtt), ha nincs kiírva a típus (pl. a main előtt), nincs mit lecserélni, így nincs hiba.

C-ben a #define-nal nem csak szimpla "szövegcserét", de függvény-szerű paraméteres makrókat is definiálhatunk. Ebben az esetben is szövegcsere történik a kódban, de a csereszöveg egyes helyeire az adott "makróhívásnál" megadott argumentumok fognak behelyettesítődni. Arra figyelni kell, hogy a makró neve és a pereméterlistát jelölő ( között nem lehet semmilyen whitespace karakter!

Feladat (f0260)

Készíts egy-egy paraméteres makrót egy szám négyzetének, két szám minimumának, maximumának, valamint négy szám minimumának és maximumának kiszámolására!

Lehetséges megoldások (f0260)
  • #define NEGYZET(X) ((X) * (X))
  • #define MINIMUM(A,B) (((A) < (B)) ? (A) : (B))
  • #define MAXIMUM(A,B) (((A) > (B)) ? (A) : (B))
  • #define MINIMUM4(A,B,C,D) MINIMUM(MINIMUM(A,B),MINIMUM(C,D))
  • #define MAXIMUM4(A,B,C,D) MAXIMUM(MAXIMUM(A,B),MAXIMUM(C,D))

Feladat (f0269)

Feladat:

Írj egy programot, ami egy láncolt listába olvas be egész számokat egy konstansként megadott végjelig, majd fordított sorrendben kiírja a beolvasott értékeket. Ha a program fordításkor a -DDEBUG kapcsolót kapja, akkor a beolvasás és kiírás során is írja ki az aktuális listaelem és a következő listaelem címét.

Ötlet (f0269)

Az előző gyakorlat f0261-es feladata jó kiindulási pont lehet.

Lehetséges megoldás (m0269.c)
 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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
/*
 * Szegedi Tudományegyetem
 * Informatikai Tanszékcsoport
 * Szoftverfejlesztés Tanszék
 *
 * Programozás Alapjai feladat megoldása
 *
 * Gergely Tamás, 2018. őszi félév.
 *
 * fordítás:
 *    gcc -o m0269 m0269.c
 *
 * futtatás:
 *    ./m0269
 */

#include <stdio.h>
#include <stdlib.h>

#define VEGJEL 0

struct cella {
    int ertek;
    struct cella *kov;
};

int main() {
    struct cella *elso = NULL;
    struct cella *p;
    int i;
    scanf("%d", &i);
    while (i != VEGJEL) {
        p        = (struct cella*)malloc(sizeof(struct cella));
        p->ertek = i;
        p->kov   = elso;
        elso     = p;
#ifdef DEBUG
        printf("akt: %p; kov: %p;\n", p, p->kov);
#endif
        scanf("%d", &i);
    }
    for (p = elso; p != NULL; p = p->kov) {
#ifdef DEBUG
        printf("akt: %p; kov: %p; ertek = %d\n", p, p->kov, p->ertek);
#else
        printf("%d\n", p->ertek);
#endif
    }
    while (elso != NULL) {
        p    = elso;
        elso = p->kov;
        free(p);
    }
    return 0;
}

Tárolási osztályok

A kapcsolódó előadásjegyzet itt található.

Feladat (f0219)

Feladat:

Vizsgáld meg a tarolas.c programot! Minimális változtatással tedd fordíthatóvá! Mi történik, ha a counter() függvényben kihagyod a static módosítót? Nézd meg a kiírt címeket is! Változtasd meg a nemvaltozo értékét úgy, hogy a deklarációját nem módosíthatod!

tarolas.c:

 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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
/*
 * Szegedi Tudományegyetem
 * Informatikai Tanszékcsoport
 * Szoftverfejlesztés Tanszék
 *
 * Programozás Alapjai
 */

#include <stdio.h>

#define MAX_NUM 5

/* A static tárolási osztály azt jelenti, hogy az adott változó megmarad a
 * blokkból kilépés után is, és a következő belépéskor elérhető lesz a
 * legutóbbi tartalom.
 * 
 * Használhatjuk arra, hogy megszámoljuk, hányszor hívtuk az adott függvényt.
 */
int* counter() {
    static int count = 0;
    count++;
    printf("Count : %d\n",count);
    return &count;
}

int* kell() {
    return counter();
}

int main() {
    /* Az auto tárolási osztály az alapértelmezett, ki sem szükséges tenni.
     */
    auto int valami = 10;

    /* A register tárolási osztály arra szolgál, hogy jelezzük a fordítónak,
     * hogy olyan gépi kódot PRÓBÁLJON meg csinálni, amely során ez a változó
     * folyamatosan a CPU egy regiszterében van. -> Gyorsabb elérésű, mint a
     * memória, de sokkal kevesebb ilyen változó létezhet. Gyakran változó
     * változót érdemes.
     *
     * A fordító figyelmen kívül hagyhatja!
     */
    register int i;

    /* A volatile módosító azt mondja a fordítónak, hogy:
     * "Vigyázat, ez a változó értéke úgy is módosulhat, hogy a kódban nincsen
     * annak módosítására szolgáló utasítás!"
     * Pl. a változó egy porthoz csatlakozik, ahová az adott eszköz írhat!
     */
    volatile unsigned short int device = (unsigned short int)0;

    /* A const módosító azt mondja a fordítónak, hogy az érték nem
     * megváltoztatható. Ez viszont csak annyit jelent, hogy az adott
     * változóhivatkozás nem szerepelhet értékadás bal oldalán.
     */
    const long int nemvaltozo = 2007;

    int *p;
    p = &i; /* EZ HIBÁS */
    p = counter();
    printf("*%p = %d\n", p, *p);
    p = kell();
    printf("*%p = %d\n", p, *p);
    *p = 100;
    counter();

    return 0;
}

Lehetséges válaszok (f0219)
  • Az 59. sor a hibás, mert a 43. sorban az i változó register tárolási osztállyal van deklarálva, így nem lesz címe. A register kulcsszó törlése javítja ezt a hibát.
  • A static módosító elhagyásával a count változó lokálisan kerül tárolásra, vagyis a függvény meghívásakor jön létre és a függvényből kilépéssel a változó megszűnik. Így viszont
    • a címe egyrészt nem lesz állandó (61. és 63. sor kiíratásában lehet ellenőrizni),
    • az értéke mindig visszaáll (60., 62. és 65. sorokból hívott kiíratás), és
    • a függvényen kívül már érvénytelen pointer lesz (a 64. sorban lévő értékadás így helytelen, futási hibát is okozhat).
  • Ha elkérjük a címét, azon keresztül átírhatjuk az értékét:
    1
    2
    p = &nemvaltozo;
    *p = 2020;
    

Összetettebb deklarációk

Feladat (f0220)

Feladat:

Fordítsd le a deklaraciok.c programot! Milyen hibákat tapasztalsz fordítás közben? Javítsd ki! Ezek után futás közben miért száll el a program? (Ha nem teszi, növeld meg N értékét, és próbáld megválaszolni azt is, hogy kisebb értékkel miért nem szállt el!)

deklaraciok.c:

 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
26
27
28
29
30
31
32
/*
 * Szegedi Tudományegyetem
 * Informatikai Tanszékcsoport
 * Szoftverfejlesztés Tanszék
 *
 * Programozás Alapjai
 */

#include <stdio.h>
#include <stdlib.h>

#define N 2048

int main() {
    const int *p = NULL;
    int * const c = NULL;
    int (*t)[N];
    int i;

    p = malloc(sizeof(int));
    *p = 2007;
    free(p);
    c = malloc(sizeof(int));
    *c = 2007;
    free(c);
    t = malloc(N * sizeof(int));
    for (i = 0; i < N; ++i) {
        *t[i] = i;
    }
    free(t);
    return 0;   
}

Parancssori argumentumok

A kapcsolódó előadásjegyzet itt található.

Feladat (f0240)

Feladat:

Írj egy programot, ami összeadja a parancssorban kapott valós számokat, és kiírja az összegüket!

Segítség (f0240)

Az atof függvény a paraméterben kapott sztring elején lévő számleírást alakítja át valós számmá.

Lehetséges megoldás (m0240.c)
 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
26
27
28
29
30
31
32
33
34
35
36
37
38
/*
 * Szegedi Tudományegyetem
 * Informatikai Tanszékcsoport
 * Szoftverfejlesztés Tanszék
 *
 * Programozás Alapjai
 *
 * Keleti Márton, 2018. őszi félév.
 *
 * Specifikáció:
 *    Input: parancssori argumentumként valós számok
 *    Output: az input valós számok összege
 *
 * Algoritmustervezés:
 *    A parancssori argumentumok eléréséhez kibővítjük a main() függvény
 *    fejlécét. Mivel a második paraméterként kapott stringtömbben a 0. elem
 *    mindig a programunk neve, a for ciklust 1-től indítjuk és az első
 *    paraméterig futtatjuk. A magjában az atof() függvényt használjuk, ami egy
 *    stringet double típusú lebegőpontos számmá alakít át.
 *
 * Fordítás:
 *    gcc -Wall -o m0240 m0240.c
 *
 * Futtatás:
 *    ./m0240
 */

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char** argv) {
    double osszeg = 0;
    for (int i = 1; i < argc; i++) {
        osszeg += atof(argv[i]);
    }
    printf("%lf\n", osszeg);
    return 0;
}

Feladat (f0237)

Problémafelvetés:

Invertálj egy PGM képet, azaz minden képpont intenzitását változtasd az ellenkezőjére (így pl. feketéből fehér, világosból sötét lesz, mint a fotónegatívokon)!

Specifikáció:

A program inputja egy (egyszerűsített) PGM formátumú kép. A PGM formátum egy szürkeárnyalatos képet ír le a következő formában. A PGM fájl első sora a P2 szöveg; a második sortól kezdve egész értékeket tartalmaz whitespace karakterekkel (szóköz, tabulátor, sortörés, stb.) elválasztva. Az első két érték X és Y, a kép szélessége és magassága, a harmadik M, a képpontok lehetséges maximális értéke. Ezt további X*Y darab [0..M] intervallumba eső érték követi, az egyes képpontok intenzitásértékei, sorfolytonosan megadva. A kimenet egy PGM formátumú kép, az eredeti kép inverze.

A képeket fájlból kell olvasni és fájlba kell kiírni! A két fájl nevét a program paramétereként kell megadni!

Ötlet (f0237)

Az előző gyakorlat f0234-es feladata jó kiindulási pont lehet.

Lehetséges megoldás (m0237-1.c)
 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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
/*
 * Szegedi Tudományegyetem
 * Informatikai Tanszékcsoport
 * Szoftverfejlesztés Tanszék
 *
 * Programozás Alapjai feladat megoldása
 *
 * Gergely Tamás, 2018. őszi félév.
 *
 * Algoritmustervezés:
 *    Az m0231-1.c megoldásból kiindulva egy minimalista programot
 *    készítünk, amelyben semmiféle hibaellenőrzés nincs, ezért nagyon
 *    csúnyán viselkedik, ha nem adunk neki két paramétert, az input fájl
 *    nem létezik, nem PGM formátumú, vagy az output fájl nem írható.
 *
 * fordítás:
 *    gcc -o m0237-1 m0237-1.c
 *
 * futtatás:
 *    ./m0237-1
 */

#include <stdio.h>

int main(int argc, char *argv[]) {
    int w, h, m;
    FILE *be, *ki;
    be = fopen(argv[1], "r");
    ki = fopen(argv[1], "w");
    fscanf(be, "P2\n%d %d %d", &w, &h, &m);
    fprintf(ki, "P2\n%d %d\n%d\n", w, h, m);
    for (int i = w * h; i > 0; --i) {
        int v;
        fscanf(be, "%d", &v);
        fprintf(ki, " %d", m - v);
    }
    fprintf(ki, "\n");
    fclose(be);
    fclose(ki);
    return 0;
}
Lehetséges megoldás (m0237-2.c)
  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
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
/*
 * Szegedi Tudományegyetem
 * Informatikai Tanszékcsoport
 * Szoftverfejlesztés Tanszék
 *
 * Programozás Alapjai feladat megoldása
 *
 * Gergely Tamás, 2018. őszi félév.
 *
 * fordítás:
 *    gcc -o m0237-2 m0237-2.c
 *
 * futtatás:
 *    ./m0237-2
 */

#include <stdio.h>
#include <stdlib.h>

/*
 * A képnek készítsünk egy adattípust. Egy szürkeárnyalatos képet kell
 * tárolnunk, melynek van szélessége (w), magassága (h), maximális képpont-
 * intenzitása (m), illetve mind a w*h pontjához tartozik egy-egy képpont-
 * intenzitási érték. Erre egy olyan struktúra felelne meg, amely három
 * egész értéket, és egy kétdimenziós, w*h méretű tömböt tartalmaz. Egy
 * struktúra tömbjét viszont csak konstans mérettel lehet deklarálni, így a
 * tömb helyett pointert használunk, amin keresztül majd akkor foglalunk
 * helyet a képpontoknak, amikor már tudjuk, hogy mennyi van. Továbbá, az
 * egyszerűbb helyfoglalás érdekében a helyet egyben foglaljuk le
 * (egydimenziós tömbként), így egy egyszerű int-re mutató pointert kapunk
 * (és nem int pointerre mutató pointert, mint valódi kétdimenziós foglalás
 * esetén). Vegyük észre, hogy a "P2" sztring a formátumhoz tartozik, így
 * nem kell tárolnunk. Egyrészt mindig ugyanaz, tehát felesleges, másrészt
 * az adatstruktúránk bármilyen szürkeárnyalatos képet képes tárolni, akkor
 * is, ha az pl. png formátumban van elmentve, mert az adattartalma akkor is
 * egy kép. (Az más kérdés, hogy a beolvasás kicsit bonyolultabb lenne.)
 */

typedef struct {
    int w;     // a kép szélessége
    int h;     // a kép magassága
    int m;     // a maximális képpont-intenzitás
    int *data; // a képpontok intenzitás-értékei
} kep_t;

/* Haladjunk modulárisan. Szükség lesz egy PGM formátumú kép betöltésére.
 * Erre készítsünk egy függvényt, ami a standard inputról olvas, és egy
 * képpel (kep_t) tér vissza. Nem csinálunk hibaellenőrzést, feltételezzük,
 * hogy egy (kommentek nélküli) PGM képet kapunk.
 */

kep_t beolvas(const char *fname) {
    int n, i;
    FILE *be;
    kep_t retval = {0, 0, 0, NULL};
    if ((be = fopen(fname, "r")) == NULL) {
        fprintf(stderr, "Hiba a %s fájl beolvasásakor!\n", fname);
        return retval;
    }
    /* Beolvassuk az első sort, a kép szélességét, magasságát, és a
     * maximális képpont-intenzitás.
     */
    if (scanf("P2\n%d%d%d", &retval.w, &retval.h, &retval.m) != 3) {
        fprintf(stderr, "A kép betöltése nem sikerült!\n");
        retval.w = retval.h = retval.m = 0;
        return retval;
    }
    n = retval.w * retval.h;        // Képpontok száma, egy segédváltozóban.
    retval.data = malloc(n * sizeof(int));
    // Ezután már be tudjuk olvasni a képpontokat, sorfolytonosan.
    for (i = 0; i < n ; ++i) {
        if (scanf("%d", &retval.data[i]) != 1) {
            fprintf(stderr, "A kép pontjainak betöltése nem sikerült!\n");
            retval.w = retval.h = retval.m = 0;
            free(retval.data);
            return retval;
        }
    }
    /* Megjegyeznénk, hogy a whitespace karakterekkel -- mint például a
     * sortörések -- nem kellett külön foglalkozni, mert a %d specifikáció
     * beolvasásnál automatikusan továbblép ezeken.
     */
    fclose(be);
    return retval;
}

/* Mivel a kép létrehozásakor dinamikusan foglaltunk memóriát, szükség lesz
 * ennek a felszabadítására. Erre is csináljunk egy függvényt.
 * Megjegyeznénk, hogy a függvény a kép adatain (a képpontok törlésén kívül)
 * nem változtat, vagyis azt, hogy egy kép törölve volt-e, futás közben
 * utólag így már lehetetlen megállapítani. Tehát a mi (programozói)
 * felelősségünk, hogy egy törölt képen már ne végezzünk semmilyen
 * műveletet.
 */

void torol(kep_t kep) {
    free(kep.data);
}

/* Beolvasás (és egyben létrehozás) valamint törlés megvolt, jöhet a kiírás,
 * szintén külön függvényben. Ez a beolvasás "tükörképe" lesz.
 */

void kiir(const char *fname, kep_t kep) {
    int n, i;
    FILE *ki;
    if ((ki = fopen(fname, "w")) == NULL) {
        fprintf(stderr, "Hiba a %s fájl írásakor!\n", fname);
        return;
    }
    printf("P2\n");                     // Kiírjuk a formátum első sortát,
    printf("%d %d\n", kep.w, kep.h);    // a kép szélességét, és magasságát,
    printf("%d", kep.m);                // a maximális képpont-intenzitást.
    n = kep.w * kep.h;                  // Képpontok száma.
    for (i = 0; i < n ; ++i) {
        /* A lenti kiírásban a trükk: a kiírt számokat szóközökkel
         * választjuk el, de minden képsor elején (amikor az index osztható
         * a kép szélességével) a szóköz helyett egy sorvége karaktert írunk
         * ki, így minden képsor adatai a kiírásban is új sorban fognak
         * kezdődni.
         */
        printf("%c%d", (i % kep.w) ? ' ' : '\n', kep.data[i]);
    }
    printf("\n");                       // Az utolsó sor lezárása.
    /* Megjegyeznénk, hogy a whitespace karakterek helyzete, vagyis hogy az
     * output hány sor (a kötelező elsőn túl), a saját döntésünk.
     */
    fclose(ki);
}

/* Már csak az invertálás maradt hátra. Ez nem túl nehéz feladat, minden
 * képpontra függetlenül végrehajtható, és csak a képpont eredeti értékétől
 * (meg persze a maximális képpont-intenzitástól függ). A kapott képen
 * fogunk dolgozni. Mivel a kép direkt adatait nem változtatjuk (csak a
 * pointer által mutatott adatokat), a képet elegendő érték szerint átvenni.
 */

void invertal(kep_t kep) {
    int n, i;
    n = kep.w * kep.h;                  // Képpontok száma.
    for (i = 0; i < n ; ++i) {
        kep.data[i] = kep.m - kep.data[i];
    }
}

/* Már csak a főprogram van hátra. */

int main(int argc, char *argv[]) {
    kep_t kep;
    if (argc < 3) {
        fprintf(stderr, "Nincs elég paraméter!\nHasználat: %s INPUT OUTPUT\n", argv[0]);
        return 1;
    }
    kep = beolvas(argv[1]);
    invertal(kep);
    kiir(argv[2], kep);
    torol(kep);
    return 0;
}

Feladat (f0247)

Problémafelvetés:

Írj egy programot ami négy háromdimenziós koordináta-hármasból kiszámítja egy pontok által határolt térrész (egyfajta szabálytalan "tetraéder", oldalaikkal és csúcsaikkal érintkező négy háromszög által határolt test, melynek csúcsai a megadott pontok) felszínét.

Specifikáció:

A szükséges adatokat a program parancssori paraméterként kapja meg.

Ötlet (f0247)

Az 5. gyakorlat f0184-es feladata jó kiindulási pont lehet.

Lehetséges megoldás (m0247.c)
 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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
/*
 * Szegedi Tudományegyetem
 * Informatikai Tanszékcsoport
 * Szoftverfejlesztés Tanszék
 *
 * Programozás Alapjai
 */

#include <stdio.h>
#include <math.h>

struct pont {
    double x;
    double y;
    double z;
};

typedef struct pont pont_t;

pont_t get_pont(const char *str);
double tavolsag(pont_t P, pont_t Q);
double terulet(pont_t A, pont_t B, pont_t C);

int main(int argc, char *argv[]) {
    pont_t P[4];
    double t[4];
    double felszin = 0.0;
    if (argc < 5) {
        fprintf(stderr, "Használat: %s '(x1,y1,z1)' '(x2,y2,z2)' '(x3,y3,z3)' '(x4,y4,z4)'\n", argv[0]);
        return 1;
    }
    for (int i = 0; i < 4; ++i) {
        P[i] = get_pont(argv[i+1]);
        t[i] = 0.0;
    }
    t[0] = terulet(P[1], P[2], P[3]);
    t[1] = terulet(P[0], P[2], P[3]);
    t[2] = terulet(P[0], P[1], P[3]);
    t[3] = terulet(P[0], P[1], P[2]);
    for (int i = 0; i < 4; ++i) {
        if (t[i] == 0.0) {
            printf("A megadott pontok egy síkba esnek.\n");
            return 1;
        }
    }
    for (int i = 0; i < 4; ++i) {
        felszin += t[i];
    }
    printf("A test felszíne: A = %.3lf\n", felszin);
    return 0;
}

pont_t get_pont(const char *str) {
    pont_t retval = {0.0, 0.0, 0.0};
    sscanf(str, "(%lf,%lf,%lf)", &retval.x, &retval.y, &retval.z);
    return retval;
}

double tavolsag(pont_t P, pont_t Q) {
    pont_t d;
    d.x = P.x - Q.x;
    d.y = P.y - Q.y;
    d.z = P.z - Q.z;
    return sqrt(d.x * d.x + d.y * d.y + d.z * d.z);
}

double terulet(pont_t A, pont_t B, pont_t C) {
    double a, b, c, s;
    a = tavolsag(B, C);
    b = tavolsag(A, C);
    c = tavolsag(A, B);
    s = (a + b + c) / 2.0;
    return sqrt(s * (s - a) * (s - b) * (s - c));
}

Modulok

A kapcsolódó előadásjegyzet itt található.

Feladat (f0262)

Feladat:

Hozz létre egy típust háromdimenziós pontok tárolására! Készíts egy függvényt, ami két ilyen térbeli pont távolságát adja vissza, valamint egy függvényt, ami három oldalhossz alapján kiszámolja egy háromszög területét! A fenti programelemekből készíts egy modulként használható lib.h és lib.c párost!

Ezek után írj egy tetra.c nevű programot ami négy háromdimenziós koordináta- hármasból kiszámítja egy pontok által határolt térrész (egyfajta szabálytalan "tetraéder", oldalaikkal és csúcsaikkal érintkező négy háromszög által határolt test, melynek csúcsai a megadott pontok) felszínét! A szükséges adatokat a program parancssori paraméterként kapja meg.

Ötlet (f0262)

Az előző f0247-es feladata jó kiindulási pont lehet.

Lehetséges megoldás (f0262)

Lib header (m0262-lib.h):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/*
 * Szegedi Tudományegyetem
 * Informatikai Tanszékcsoport
 * Szoftverfejlesztés Tanszék
 *
 * Programozás Alapjai
 */

#ifndef M0262_LIB_H
#define M0262_LIB_H

struct pont {
    double x;
    double y;
    double z;
};

typedef struct pont pont_t;

pont_t get_pont(const char *str);
double tavolsag(pont_t P, pont_t Q);
double terulet(pont_t A, pont_t B, pont_t C);

#endif //M0262_LIB_H

Implementáció (m0262-lib.c):

 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
26
27
28
29
30
31
32
33
34
35
/*
 * Szegedi Tudományegyetem
 * Informatikai Tanszékcsoport
 * Szoftverfejlesztés Tanszék
 *
 * Programozás Alapjai
 */

#include <stdio.h>
#include <math.h>

#include "m0262-lib.h"

pont_t get_pont(const char *str) {
    pont_t retval = {0.0, 0.0, 0.0};
    sscanf(str, "(%lf,%lf,%lf)", &retval.x, &retval.y, &retval.z);
    return retval;
}

double tavolsag(pont_t P, pont_t Q) {
    pont_t d;
    d.x = P.x - Q.x;
    d.y = P.y - Q.y;
    d.z = P.z - Q.z;
    return sqrt(d.x * d.x + d.y * d.y + d.z * d.z);
}

double terulet(pont_t A, pont_t B, pont_t C) {
    double a, b, c, s;
    a = tavolsag(B, C);
    b = tavolsag(A, C);
    c = tavolsag(A, B);
    s = (a + b + c) / 2.0;
    return sqrt(s * (s - a) * (s - b) * (s - c));
}

Főprogram (m0262-tetra.c):

 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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
/*
 * Szegedi Tudományegyetem
 * Informatikai Tanszékcsoport
 * Szoftverfejlesztés Tanszék
 *
 * Programozás Alapjai
 */

#include <stdio.h>

#include "m0262-lib.h"

int main(int argc, char *argv[]) {
    pont_t P[4];
    double t[4];
    double felszin = 0.0;
    if (argc < 5) {
        fprintf(stderr, "Használat: %s '(x1,y1,z1)' '(x2,y2,z2)' '(x3,y3,z3)' '(x4,y4,z4)'\n", argv[0]);
        return 1;
    }
    for (int i = 0; i < 4; ++i) {
        P[i] = get_pont(argv[i+1]);
        t[i] = 0.0;
    }
    t[0] = terulet(P[1], P[2], P[3]);
    t[1] = terulet(P[0], P[2], P[3]);
    t[2] = terulet(P[0], P[1], P[3]);
    t[3] = terulet(P[0], P[1], P[2]);
    for (int i = 0; i < 4; ++i) {
        if (t[i] == 0.0) {
            printf("A megadott pontok egy síkba esnek.\n");
            return 1;
        }
    }
    for (int i = 0; i < 4; ++i) {
        felszin += t[i];
    }
    printf("A test felszíne: A = %.3lf\n", felszin);
    return 0;
}

Gyakorló feladatok

Feladat (f0153)

Feladat:

Készíts egy programot, amely kiírja egy \(4 \times 4\)-es egész mátrix transzponáltját, majd módosítsd a programot úgy, hogy \(2 \times 2\)-es, majd \(3 \times 3\)-as mátrixokkal dolgozzon! Mindegyik változatot futtasd le!

  • Hány ponton kellett megváltoztatni a programot? Ha egynél több ponton, akkor írd meg úgy, hogy a mátrix méretváltoztatása miatt csak egy helyen kelljen belenyúlni!
  • Preprocesszáld a forráskódot, és az eredményt hasonlítsd össze az eredeti forráskóddal! Az elejére bekerült könyvtári kódon kívül mi változott?
Ötlet (f0153)

A 7. gyakorlat f0135-ös feladata jó kiindulási pont lehet.

Feladat (f0238)

Problémafelvetés:

Olvass be egy PGM képet, és tedd fekete-fehérré, azaz a sötétebb pontokból csinálj feketét, a világosabbakból pedig fehéret.

Specifikáció:

A program inputja egy (egyszerűsített) PGM formátumú kép. A PGM formátum egy szürkeárnyalatos képet ír le a következő formában. A PGM fájl első sora a P2 szöveg; a második sortól kezdve egész értékeket tartalmaz whitespace karakterekkel (szóköz, tabulátor, sortörés, stb.) elválasztva. Az első két érték X és Y, a kép szélessége és magassága, a harmadik M, a képpontok lehetséges maximális értéke. Ezt további X*Y darab [0..M] intervallumba eső érték követi, az egyes képpontok intenzitásértékei, sorfolytonosan megadva. A kimenet is egy PGM formátumú kép, az eredeti kép fekete-fehérré alakított változata (amelyben így csak 0 és M intenzitású képpontok szerepelnek).

A képeket fájlból kell olvasni és fájlba kell kiírni. A két fájl nevét a program paramétereként kell megadni.

Ötlet (f0238)

Az előző gyakorlat f0235-ös feladata jó kiindulási pont lehet.

Feladat (f0251)

Problémafelvetés:

Készíts egy programot, amely egy tetszőleges nagyságú számról a jól ismert oszthatósági szabályok felhasználásával eldönti, hogy a szám osztható-e a \([2..12]\) intervallumba eső számok bármelyikével!

Specifikáció:

Az ellenőrizendő értéket a program parancssori paraméterként kapja meg.

Ötlet (f0251)

A 6. gyakorlat f0166-os feladata jó kiindulási pont lehet.

Feladat (f0254)

Problémafelvetés:

Rendezzünk sztringeket lexikografikus rendezés szerinti sorba!

Specifikáció:

A sorbarendezendő értékeket a program parancssori paraméterként kapja meg.

Algoritmustervezés/Megvalósítás:

Használjunk indextömböt a paraméterként kapott sztring pointer tömb direkt manipulálása helyett. (Az \(I[ ]\) indextömb arra jó, hogy ne egy \(A[ ]\) tömb elemeit kelljen cserélgetni. Ha például az \(A[ ]\) tömb \(i\). és \(j\). elemét kellene megcserélni, akkor az \(A[i] \leftrightarrow A[j]\) csere helyett az \(I[i] \leftrightarrow I[j]\) cserét hajtjuk végre, és az \(A[ ]\) \(k\). elemét az \(A[I[k]]\) összetett indexeléssel érjük el.)

További feladatok

Feladat (f0239)

Problémafelvetés:

Tükrözz egy PGM képet a függőleges tengelyére!

Specifikáció:

A program inputja egy (egyszerűsített) PGM formátumú kép. A PGM formátum egy szürkeárnyalatos képet ír le a következő formában. A PGM fájl első sora a P2 szöveg; a második sortól kezdve egész értékeket tartalmaz whitespace karakterekkel (szóköz, tabulátor, sortörés, stb.) elválasztva. Az első két érték X és Y, a kép szélessége és magassága, a harmadik M, a képpontok lehetséges maximális értéke. Ezt további X*Y darab [0..M] intervallumba eső érték követi, az egyes képpontok intenzitásértékei, sorfolytonosan megadva. A kimenet is egy PGM formátumú kép, az eredeti kép függőleges tengelyre tükrözött változata.

A képeket fájlból kell olvasni és fájlba kell kiírni. A két fájl nevét a program paramétereként kell megadni.

Ötlet (f0239)

Az előző gyakorlat f0236-es feladata jó kiindulási pont lehet.

Feladat (f0243)

Feladat:

Írj egy programot, ami kiszámolja a Fibonacci sorozat \(n.\) elemét minden parancssori paraméterben kapott egész értékre! A sorozat elemei: \(a_0=1, a_1=1\) és \(n>1\) esetén \(a_n = a_{n-1} + a_{n-2}\).

Ötlet (f0243)

A 7. gyakorlat f0118-as feladata jó kiindulási pont lehet.

Feladat (f0246)

Feladat:

Írj egy programot, ami minden a felhasználó által a parancssorban megadott egész számról megállapítja, hogy prím-e, és ha nem, kiírja a különböző pozitív osztóinak számát és összegét!

Feladat (f0250)

Problémafelvetés:

Írj programot ami meghatározza, hogy a megadott pénzösszegek hogyan fizethetők ki lehető legkevesebb 200, 100, 50, 20, 10, 5, 2 és 1 koronás érmével!

Specifikáció:

A felváltandó összegeket a program parancssori paraméterként kapja meg.

Ötlet (f0250)

A 2. gyakorlat f0025-ös feladata jó kiindulási pont lehet.

Feladat (f0255)

Problémafelvetés:

Számítsuk ki két vektor skalár és mátrixszorzatát.

Specifikáció:

A két vektort a program parancssori paraméterként kapja meg.

Ötlet (f0255)

A 8. gyakorlat f0229-es és f0265-ös feladata jó kiindulási pont lehet.

Feladat (f0241)

Feladat:

Írj egy programot, amely n-szer egymás után fűzi ugyanazt az s sztringet, ahol n és s értékét is parancssori paraméterként kapja meg.

Feladat (f0244)

Feladat:

Írj egy programot, ami kiszámolja az általánosított \(k\)-ad rendű Fibonacci sorozat \(n\). elemét. A \(k\) és \(n\) értékét parancssori paraméterben kapja meg a program. A program csak helyes paraméterezés esetén (két egész értékű paraméter esetén) adjon eredményt. Az általánosított \(k\)-ad rendű Fibonacci első \(k\) eleme \(1\) (azaz \(a_0 = \dots =a_{k-1} = 1\)), a többi elemét pedig az előző \(k\) elem összegeként kapjuk, (azaz \(a_n = a_{n-1} + \dots + a_{n-k}\)).

Ötlet (f0244)

A 9. gyakorlat f0224-es feladata jó kiindulási pont lehet.

Feladat (f0248)

Problémafelvetés:

Írj egy programot, ami \(g\) nehézségi gyorsulás mellett szimulálja egy \(\alpha\) kilövési szögben és \(v\) kezdősebességgel kilőtt test röppályáját légüres térben sík terepen. A felhasználó adja meg a kilövési paramétereken túl azt is, hogy a kezdő és végpontokon kívül teljes repülési időt hány egyenlő részre osztva kell meghatározni és kiírni a test helyzetét (\(x,y\) koordinátáit).

Specifikáció:

A szükséges adatokat a program parancssori paraméterként kapja meg.

Ötlet (f0248)

A 7. gyakorlat f0204-es feladata jó kiindulási pont lehet.

Feladat (f0252)

Problémafelvetés:

Írj egy programot ami sorbarendezi a megadott valós értékeket.

Specifikáció:

A sorbarendezendő értékeket a program parancssori paraméterként kapja meg.

Feladat (f0256)

Problémafelvetés:

Írj egy programot, ami kiszámolja egy konvex sokszög súlypontját.

Specifikáció:

A program a csúcspontok koordinátáit parancssori paraméterként kapja meg.

Ötlet (f0256)

A 5. gyakorlat f0226-os feladata jó kiindulási pont lehet.

Feladat (f0242)

Feladat:

Írj egy programot, ami kiszámolja \(n!\) értékét minden parancssori paraméterben kapott egész értékre.

Ötlet (f0242)

A 7. gyakorlat f0126-os feladata jó kiindulási pont lehet.

Feladat (f0245)

Feladat:

Írj egy programot, ami kiszámolja a felhasználó által a parancssorban megadott egész számsorozat legnagyobb közös osztójának értékét.

Feladat (f0249)

Problémafelvetés:

Adott két körszerű test a kétdimenziós síkon. A testek helyzetét a középpontjaikkal azonosítjuk. Kezdetben a felhasználó mindkét testhez megadja:

  1. a középpontját \(x,y\) koordinátákkal,
  2. sugarát,
  3. kezdősebességét és annak irányát, valamint
  4. a test tömegét.

Megad továbbá egy \(\Delta t\) és egy \(T\) időintervallumot. A feladat a két test kétdimenziós fizikai mozgásának szimulálása a \(0..T\) időintervallumban \(\Delta t\) időbeosztással. Ez azt jelenti, hogy a kezdő időpontban kiszámoljuk a testekre ható erőket, majd a test mozgását a következő \(\Delta t\) időintervallumban úgy közelítjük, mintha a testre ebben az időintervallumban nem hatna egyéb erő. Ezután újra kiszámítjuk az erőket, majd újra közelítjük a mozgást, stb. A mozgás szimulálása a \(T\) időpontig vagy a két test "ütközéséig" tart. A pontosság kedvéért a két test helyzetét (\(x,y\) koordinátáit) úgy írassuk ki, mintha az origo, azaz a koordinátarendszer középpontja a két test közös tömegközéppontja lenne, azaz a kiszámított koordinátaértékeket ezek szerint korrigáljuk minden lépésben.

Specifikáció:

A szükséges adatokat a program parancssori paraméterként kapja meg.

Ötlet (f0249)

A 5. gyakorlat f0206-os feladata jó kiindulási pont lehet.

Feladat (f0253)

Problémafelvetés:

Rendezzünk sztringeket lexikografikus rendezés szerinti sorba!

Specifikáció:

A sorbarendezendő értékeket a program parancssori paraméterként kapja meg.

Algoritmustervezés/Megvalósítás:

Használjuk a main paramétereként kapott sztring tömböt, az ebben lévő pointerek átsorrendezésével oldjuk meg a feladatot.

Feladat (f0257)

Feladat:

Készíts olyan programot, mely összefésüli a paraméterben kapott sztringeket. Az összefésülés azt jelenti, hogy az eredményben először a sztringek első karakterei, majd második karakterei, majd harmadik karakterei, stb. vannak. Ha valamelyik sztring rövidebb a többinél, akkor őt a további iterációknál nem kell figyelembe venni.


Utolsó frissítés: 2022-11-25 15:25:14