Programozási tételek

1 Emlékeztető: feladatok és eszközök

Szekvencia

printf("Mi a szám? ");
scanf("%d", &a);
a = a*a;
printf("Négyzete: %d", a);

Elágazás

if (szam>0)
   printf("pozitív");
else
   printf("nem pozitív");

Ciklus

for (i=1; i<=10; i=i+1)
   printf("i=%d\n", i);

Algoritmus

  • Programozó: algoritmust tervez a feladat megoldására
  • Többé-kevésbé általános
  • Véges számú lépésben fut

Vezérlési szerkezetek

  • Számítási folyamat leírása: mikor mi történjen
  • Már ismeritek a működést, sejtitek, mi lesz az eredmény

Példák az általánosság fogalmához:

  • Prímszámok: szám → igaz/hamis. Első 100 prímet tudja? Jó, de nem elég általános. Osztók próbálgatása: ez jobb megoldás!
  • Böngészőprogram: leíró kód → megjelenített oldal. Bemenet: szöveg, színek, margók, betűméretek, … Kimenet: a formázott oldal képe.

2 Az állásinterjús kérdés: fizz buzz

fizz
buzz
11
fizz
13
14
fizzbuzz
16
1
2
fizz
4
buzz
fizz
7
8

Fizz buzz: a feladat

Mondjuk sorban a számokat, de ha


Az oszthatóság vizsgálata

==
egyenlő-e
%
maradék
/* osztható? a maradék nulla? */
if (szam%3 == 0)
    printf("fizz\n");

3 Fizz buzz: 3-mal és 5-tel is

&&
„és”: mindkét
feltétel teljesül
/* 3-mal és 5-tel is osztható */
if (szam%3 == 0 && szam%5 == 0)
    printf("fizzbuzz\n");

Vigyázat! Melyik feltétel is teljesül 15-nél???


A fizzbuzz probléma Karnaugh-táblája
Az „és” kapcsolat

A feltételek átfedése miatt nem mindegy, melyiket használjuk melyik következményeként és alternatívájaként. Ha együtt nem teljesülnek, még mindig lehet, hogy külön-külön igen. Nem lenne jó itt felsorolni az összes rossz megoldást, és megmagyarázni mindegyikről, hogy mi a bajuk – az egy egyszerű nyomkövetéssel észrevehető. Mindenki kipróbálhatja magának!

Azért szerepel itt ez a példa, mert kedvenc kérdés szokott lenni az állásinterjúkon, és meglepő, hogy mennyiszer elrontják a jelentkezők. (Lásd itt és itt.) Hogyan függenek össze a feltételek? Hogyan kell egymásba tenni a vezérlési szerkezeteket? Jó-e az, ha leírjuk a három vizsgálatot (3-mal, 5-tel, mindkettővel), mindenhova közé else-t téve? Észre kell venni, hogy a feladat szövegében említett három eset igazából négyet jelent, amelyek pedig átfedik egymást.


Fizz buzz döntések folyamatábrája
#include <stdio.h>

int main()
{
   int szam;

   for (szam=1; szam<=20; szam=szam+1)
      if (szam%3 == 0 && szam%5 == 0)
         printf("fizzbuzz\n");
      else
         if (szam%3 == 0)
            printf("fizz\n");
         else
            if (szam%5 == 0)
               printf("buzz\n");
            else
               printf("%d\n", szam);

   return 0;
}

Mivel minden feltétel igaz és hamis ágában csak egy további utasítás van (egy következő if is egy utasításnak számít), ezért itt nem volt szükség az utasítások {} blokkba helyezésére.

A C nyelvtani szabályai szerint az else utasítás mindig az azt megelőző, legközelebbi if-hez tartozik. Ha ezt módosítani szeretnénk, az természetesen lehetséges, az utasítások megfelelő {} blokkba helyezésével. Így:

if (feltétel1) {
   if (feltétel2)
      printf("akkor, ha feltétel1 és feltétel2");
} else
   printf("akkor, ha nem feltétel1");

Sokan egyébként az önálló utasításokat is blokkba teszik, és nem írnak a fentihez hasonló kódot. Néha hosszabb kicsit úgy, de sok előnye van.

Programozási tételek

5 Sorozatok és tételek

Sorozatok (nem a Dallas)


Programozási tételek

Általánosságban megfogalmazott algoritmusok; mindig kicsit alakítjuk a konkrét feladatunkhoz.

6 Összegzés tétele

Összesítsük a rendeléseket!

Írjunk programot, amely összegzi a fogyasztásunkat: a felhasználótól kapott pozitív, egész számokat összegez. Addig, amíg −1-et nem kap.

2 + 3 + 1 = ?

Összegzés tétele

összeg=0                      akkumulátor
CIKLUS AMÍG van még szám, ADDIG
    szám = következő elem
    összeg = összeg+szám
CIKLUS VÉGE

Az „akkumulátor” változó az, amelyikben összegyűlik, akkumulálódik az eredmény. Ezt először nullázzuk, utána minden feldolgozott számot hozzáadjuk. Minden iteráció végén az addig látott számok összegét fogja így tartalmazni. Ha esetleg egyszer sem ment volna be a ciklusba, akkor pedig nullát.

A végjeles sorozat kezelése: a beolvasás helye

  • A ciklus feltétele ez lesz: AMÍG szám ≠ −1, …
  • Ez a beolvasott számtól függ → már az első előtt lennie kell beolvasásnak
  • De mindig kell egy új szám → a cikluson belül is

Helyes megoldás (a tétel alkalmazása)

„az első
különleges”
összeg = 0
BE: szám              első
CIKLUS AMÍG szám≠−1, ADDIG
   összeg = összeg+szám
   BE: szám           következő (többi)
CIKLUS VÉGE

Ez egy nagyon fontos rész. Itt a ciklus működését meg kell érteni! A ciklusfeltétel ellenőrzi azt, hogy a kapott szám −1-e, vagy nem. Mivel ez egy elöltesztelő ciklus, ezért ennek a feltételnek az ellenőrzése a ciklusba belépés előtt fog megtörténni. Ez azt jelenti, hogy a ciklus első elérésekor már rendelkeznünk kell egy számmal, ami a felhasználótól származik, vagyis kell lennie egy beolvasásnak a ciklus előtt.

Namármost, ha a feltétel igaz, akkor bekerül a végrehajtás a ciklus belsejébe. Ilyenkor éppen van egy számunk, amit a billentyűzetről kaptunk, és ami nem −1, ezért azt hozzá kell adnunk az összeghez. Itt azt általánosan megfogalmazott összegzés tételét át kell alakítanunk a jelenlegi, konkrét feladatunkhoz, hiszen a ciklustörzs nem egy beolvasással kezdődik. Helyette az összeadással, mert a számunk már megvan.

A ciklustörzs egy újabb beolvasással végződik. Ami első ránézésre olyan, mintha a következő beolvasott számmal már nem csinálnánk semmit, de ez nincs így! A ciklustörzs végrehajtása után a vezérlés újra a ciklusfeltétel ellenőrzéséhez kerül. Ilyenkor már az új számot fogja ellenőrizni a feltétel újbóli kiértékelése – és ha igaznak adódott, vagyis ha a szám nem −1, akkor már az új szám fog az összeghez hozzáadódni. A ciklustörzs végén álló beolvasás a következő iteráció számára készíti elő a terepet. Vegyük észre, hogy ilyenkor a „terep” pont ugyanúgy néz ki, mint az első végrehajtás előtt. Van egy számunk, amit meg kell vizsgálni, hogy −1-e, és ha nem, akkor hozzáadni az összeghez.

A fentiek végiggondolását kezdő és haladó programozóknak is ajánljuk. Ennek a problémának ez A Szép Megoldása.

!=
nem egyenlő
#include <stdio.h>

int main()
{
   int osszeg, szam;

   printf("Kérem a számokat, -1: vége\n");

   osszeg=0;              // elején nulla
   scanf("%d", &szam);
   while (szam!=-1) {
      osszeg=osszeg+szam; // ha van szám, hozzáad
      scanf("%d", &szam);
   }

   printf("Összeg: %d\n", osszeg);

   return 0;
}

7 Összegzés? Faktoriális!

C rövidítések:
a=a*b → a*=b
a=a+b → a+=b
stb.
#include <stdio.h>

int main()
{
   int i, n, szorzat;

   printf("Melyik szám a faktoriálisa? ");
   scanf("%d", &n);

   szorzat=1;
   for (i=1; i<=n; i+=1)
      szorzat*=i;       // szorzat = szorzat*i

   printf("%d faktoriálisa %d\n", n, szorzat);

   return 0;
}

Más a művelet, de ugyanaz az elv: ciklusban akkumulálunk!

Az összegzés és a faktoriális egymás mellett:

összeg=0
CIKLUS AMÍG van szám, ADDIG
    szám = következő elem
    összeg = összeg+szám
CIKLUS VÉGE
szorzat=1
CIKLUS i=1-től n-ig

    szorzat = szorzat*i
CIKLUS VÉGE

Csak lecseréltük:

  • A kezdeti értéket 0-ról 1-re
  • Az összeadást szorzásra
  • A ciklust számlálásosra (1→n, ez előre adott hosszúságú sorozat)
  • A számot nem kell beolvasni, hiszen benne van az i változóban

Algoritmikai szempontból a kettő tökéletesen ugyanaz. A szorzat változó tölti be az akkumulátor szerepét, az i pedig az iterátor, amelyre a ciklus szervezése épül.

8 Számlálás tétele: osztók száma

Feladat

Számoljuk meg, hogy egy számnak hány osztója van. (1 és saját maga is.)

1  2  3  4  5  6  7  8  9  10  11  12

Megoldás gondolatmenete

  • Legegyszerűbb: próbálgatás
  • CIKLUS 1-től a számig
  • HA osztható, AKKOR növelünk egy számlálót
  • A számláló kezdeti értéke 0

Vegyük észre: ez egy előre adott hosszúságú sorozat. 1-től az adott számig kell eljutni.


Számlálás tétele

db=0
CIKLUS AMÍG van még szám, ADDIG
    szám = következő elem
    HA igaz a feltétel szám-ra, AKKOR    melyikeket kell megszámolni?
        db = db+1
    FELTÉTEL VÉGE
CIKLUS VÉGE

További példák

  • Hány páros számot gépeltek be?
  • Hány osztója van egy számnak?
  • Hány „e” betű van benne?
#include <stdio.h>

int main()
{
   int szam, oszto, db;

   printf("Kérem a számot: ");
   scanf("%d", &szam);

   db=0;               // kezdetben 0
   for (oszto=1; oszto<=szam; oszto+=1)
      if (szam%oszto==0)
         db+=1;        // ha ez osztója, +1

   printf("Összesen %d osztója van.\n", db);

   return 0;
}

9 A karakter típus – feladat

Feladat

Számoljuk meg, a begépelt szövegben hány „e” betű van!


Karakterek (character)

    0123456789

 30   ␣!"#$%&'
 40 ()*+,-./01
 50 23456789:;
 60 <=>?@ABCDE
 70 FGHIJKLMNO
 80 PQRSTUVWXY
 90 Z[\]^_`abc
100 defghijklm
110 nopqrstuvw
120 xyz{|}~

10 A karakterek kezelése C-ben

'
aposztróf
„apostrophe”
char betu;

betu='A';  betu=65;  // ugyanaz!

betu+=1;             // következő: A→B

x='c'-'a';           // távolság: 2, mert a→b→c

if (betu>='a' && betu<='z') {
   printf("Ez egy kisbetű!\n");
   betu=betu-'a'+'A';         // nagybetű lesz belőle
}

printf("%c betű kódja %d", 88, 88); // „X betű kódja 88”
scanf("%c", &betu);

11 Számlálás tétele: „e” és „E” betűk

||
„vagy”: valamelyik
feltétel teljesül
(elég az egyik)
A „vagy” kapcsolat
#include <stdio.h>

int main()
{
   int db, szken;
   char c;

   db=0;
   szken=scanf("%c", &c);
   while (szken==1) {        // amíg nincs vége
      if (c=='e' || c=='E')
         db+=1;              // ha megfelel, növeli
      szken=scanf("%c", &c);
   }
   printf("%d darab e betű volt.\n", db);

   return 0;
}

A programban kihasználjuk azt, hogy a scanf() jelzi a beolvasás sikerességét is. A kért karakteren kívül ugyanis ad még egy számot (ezt tárolják el a szken=scanf(...) sorok), amelynek az értéke 1, ha sikerült az egy karakter a beolvasása.

A program a két feltételét (kis „e” betű-e, nagy „E” betű-e) VAGY kapcsolatba hozva használtuk. Ez azt jelenti, hogy bármelyik megfelel számunkra. Akár kis „e” betű van, akár „E” betű, a számlálót megnöveljük. A pongyolán megfogalmazott feladatkiírás szólhatna úgy, hogy „számoljuk meg a kicsi és a nagy E betűket” – hiába tudjuk, hogy nem lehet egy betű egyszerre kicsi és nagy is.

A programok írásakor a logikai VAGY és logikai ÉS kapcsolatok közötti különbséget mindig pontosan át kell gondolni. Azért fontos ez, mert a köznapi beszédben a kettőt sokszor pont fordítva használjuk. Például elhangozhat egy tankörben a következő mondat: „Tegye fel a kezét, aki Budapesten és Debrecenben született!” Nyilvánvaló, hogy senki nem születhetett egyszerre Budapesten ÉS (logikai ÉS) Debrecenben. Egyszerűen csak ezt a gondolatot rövidítjük: „Tegye fel a kezét mindenki, aki Budapesten született, és tegye fel a kezét az is, aki Debrecenben született!” A matematikailag, és ezért a programjainkban is korrekt változat élő beszédben szokatlanul hangzana: „Tegye fel a kezét mindenki, aki vagy Budapesten, vagy Debrecenben született!”

12 Szélsőérték keresése: a leg…

Melyik a legmagasabb rakéta?

Olvassunk be a billentyűzetről a magasságokat! Hány darabot? Kérdezzük a felhasználótól az elején! Melyik volt a legnagyobb közülük?


Szélsőértékkeresés tétele

legnagyobb=első elem             első
CIKLUS AMÍG van még szám, ADDIG
   szám = következő elem        többi
   HA szám>legnagyobb, AKKOR
       legnagyobb=szám
   FELTÉTEL VÉGE
CIKLUS VÉGE

A maximumkeresés C kódrészlete

printf("Hány szám lesz? ");
scanf("%d", &db);

printf("1. szám: ");              // maximumkeresés
scanf("%lf", &aktualis);
max=aktualis;              /* az első külön! */

for (i=2; i<=db; i+=1) {   /* a többit ciklusban */
   printf("%d. szám: ", i);
   scanf("%lf", &aktualis);
   if (aktualis>max)       /* nagyobb az eddigieknél? */
      max=aktualis;
}

printf("Legnagyobb: %lf\n", max); // eredmény

Vigyázat! Az első „tippet” is a sorozatból kell venni! Általános esetben elvi hibás a legnagyobb=−1000 kezdetű vagy hasonló megoldás! (Ha az összes szám kisebb lenne −1000-nél, akkor hibás lenne az eredmény.)

13 Lineáris keresés

Feladat (általános megfogalmazásban)

Megtalálni egy elemet egy sorozatban.
Például: prímszám-e. Ahogy találunk egy osztót, tudjuk, hogy nem prímszám.


A lineáris keresés tétele

találat=HAMIS
CIKLUS AMÍG van elem ÉS NEM találat van elem és nincs találat
   szám=következő elem
   HA szám=keresett, AKKOR
      találat=IGAZ                 megvan: leáll a keresés
   FELTÉTEL VÉGE
CIKLUS VÉGE

A ciklus után a találat változó tartalmazza az eredményt: igaz vagy hamis.

14 A logikai típus C-ben

Logikai típus


!
tagadás
int kisebb, nagyobbegyenlo;

kisebb = 5 < 7;            // igaz
if (kisebb)
   printf("kisebb\n");

nagyobbegyenlo = !kisebb;  // hamis lesz
if (nagyobbegyenlo)
   printf("nagyobb vagy egyenlő\n");

Vegyük észre, hogy ez a trükk be van építve a nyelvbe: az if() igaz ága akkor hajtódik végre, ha a feltételben lévő „szám” nem nulla. A logikai típusú értékre kiértékelődő kifejezések (! – tagadás, && – és kapcsolat, < – kisebb, mint stb.) igaz érték esetén 1-et adnak, hamis érték esetén 0-t. Vagyis ha valamilyen C nyelvi kifejezés igaz/hamis értéket állít elő, akkor 0-t vagy 1-et kapunk, de egyébként bármilyen nem nulla számot elfogad a C a logikai igaz érték reprezentációjának.

Fontos ezt a különbséget megérteni. A logikai típus egy külön típus, hiszen saját értékkészlete van (hamis, igaz), és saját műveletei (tagadás, és, vagy stb.). Csak éppenséggel a C nyelvben nincs a típusnak külön neve, hanem az egyszerűség kedvéért nulla és nem nulla egész számokkal jelképezzük azt. A legtöbb azóta létrejött nyelvben külön neve van ennek a típusnak, de a C megközelítése szinte mindegyikre hatással volt.

15 Lineáris keresés: prímszám-e (C kód)

int szam, oszto, vanoszto;

printf("Kérem a számot: ");
scanf("%d", &szam);

vanoszto=0;          // 0: hamis
oszto=2;
while (oszto<szam && !vanoszto) {
   if (szam%oszto==0)
      vanoszto=1;    // 1: igaz
   oszto+=1;
}

if (vanoszto)        // volt találat?
   printf("Nem prím.\n");
else
   printf("Prím.\n");

Ha el kell dönteni egy számról, hogy prímszám-e, sokkal értelmesebb dolog a lineáris keresés tételét alkalmazni, mint a számlálás tételét. Mondhatjuk ugyan, hogy megszámoljuk a szám osztóit, és ha csak kettő (egy és saját maga), akkor az egy prímszám. De miért kellene a kérdés megválaszolásához megvizsgálni az összes osztót? Miért ne állnánk meg már az elsőnél, azt mondva, hogy kérem szépen, ez nem prímszám?

Egyébként az osztókat elég lenne a szám feléig vizsgálni (hiszen ha osztója a fele, akkor osztója 2 is), sőt elég lenne a gyökéig (ugyanígy).

16 Tételek kombinációja: a klasszikus példa

Számoljuk meg, hány prímszám van 2 és 1000 között!


Megoldás

  • Számoljuk meg → számlálás tétele
  • Prímszám-e: „van-e osztója” → lineáris keresés tétele
Számlálás tétele
int sz, db;
db=0;
for (sz=2; sz<=1000; sz+=1)
   if (… sz egy prím …)
      db+=1;
Lineáris keresés tétele
int oszt, van;
van=0;
oszt=2;
while (oszt<sz && !van) {
   if (sz%oszt==0)
      van=1;
   oszt+=1;
}
Teljes megoldás
int sz, db, oszt, van;

db=0;
for (sz=2; sz<=1000; sz+=1) {
   van=0;
   oszt=2;
   while (oszt<sz && !van) {
      if (sz%oszt==0)
         van=1;
      oszt+=1;
   }
   if (!van)
      db+=1;
}

printf("%d prím.\n", db);

A két tételt összedolgozzuk. A kettő közötti kapcsolatot a van változó teremti meg; a lineáris keresés eredménye ide kerül. Ha nincs osztó, prímről van szó, növelni kell a darabszámot. Látható, hogy a keletkezett kód ugyan strukturált, de kusza lett. A két tétel „egymásba folyik”, hiába van szó matematikailag egy két nagyon jól elkülönülő fogalomról. Kellene valamilyen eszköz, amellyel szét tudjuk választani a két részt. Ezek lesznek a függvények a következő előadáson.

Tömbök

18 Tíz darab szám

Feladat

Kérjen a felhasználótól 10 számot, és utána írja ki őket fordított sorrendben!


Megoldás – sorminta???

int a, b, c, d, e, f, g, i, j, k;

scanf("%d", &a);
scanf("%d", &b);
scanf("%d", &c);
…
printf("%d\n", c);
printf("%d\n", b);
printf("%d\n", a);

Mire lenne itt szükség?

Az eddigi programjainkban:

  • Csak néhány nevesített változóval dolgoztunk, amelyeknek mind kitüntetett szerepe volt
  • Nem tudtuk azt mondani, hogy „sok”
  • Csak a beérkezés sorrendjében tudtuk feldolgozni az adatokat

Ami hiányzik:

  • Jó lenne egyszerre több elemet is tárolni
  • Az elemeket sorszámozva hivatkozni (első szám, második szám…), mert akkor egy ciklus végigmehetne az elemeken
  • Az elemeket tetszőleges sorrendben elérni, mert akkor kiírhatnánk fordított sorrendben

19 A tömb

Tömb (array)

  • Egyforma típusú változókból álló, fix méretű tároló (container)
  • Más néven: vektor (vector)
  • A típus bármi lehet, pl. egészek tömbje, valósak tömbje…
  • Egységesen kezelhetőek, mert az elemek indexelhetőek
  • Legtöbb programozási nyelvben a számozás 0-tól kezdődik
a0a1a2a3a4a5
99713-454712

Szóhasználat

20 A tömbök kezelése I. – hogyan

Tömb létrehozása: elemtípus név[méret];
Kezdeti érték: {} között, nem kötelező.

int tomb[10];
double t[5] = {9.3, 7.5, 3.7, 0, 4.2};

Ha adunk meg kezdeti értéket, akkor legalább egy elemet írnunk kell a kapcsos zárójelek közé. Ha kevesebbet írunk, mint a tömb mérete, akkor a többi nulla lesz. Viszont ha egyáltalán nem adunk meg kezdeti értéket, akkor a tömb elemei inicializálatlanok! (Erről még lesz pár szó.)


Elem elérése: indexelés (indexing) vagy címzés, szögletes zárójellel (bracket).

tomb[9]=3;
printf("%d", tomb[9]);

Tömb feldolgozása: ciklussal. Index tartománya: 0-tól méret−1-ig!

Dijkstra
for (i=0; i<10; i+=1)
   tomb[i]=0;

Ez a tipikus tömbös ciklus. Nullától indul az iterátor (ez a tömb legelső eleme), és egyesével növekszik. A ciklusban maradás feltételében (i<10) a „kisebb” relációt szokás használni, nem pedig a „kisebb vagy egyenlő” relációt, mégpedig azért, mert így a tömb mérete szerepel a kódban. Bár i<30 és i≤29 ugyanazt jelenti, de az i<30 forma sokkal egyszerűbb! Nem kell figyelni arra, hogy kivonjunk egyet a tömb méretéből, hanem magát a méretet lehet odaírni. Szokjuk meg ezt a formát a tömbökhöz, az egész világon így csinálják!

Érdekesség: Edsger W. Dijkstra holland matematikus, programozó volt. Fontosnak tartotta a levelezést és a tapasztalatcserét kollégáival. Ezért a gondolatait, megfigyeléseit, útjairól szóló írásait számozva, fénymásolt kéziratok formájában küldte el nekik. A fentiekkel kapcsolatban álljon itt egy rövid írása (EWD831).

21 A tömbök kezelése II. – hogyan ne


A tömb elemeit csak egyesével lehet kezelni.

Helytelen:
int a[10], b[10];
a=b;
Helyes:
for (i=0; i<10; i+=1)
   a[i]=b[i];

Az a=b értékadás helytelen voltának mélyebb okai vannak. Erről később lesz szó.


A tömb méretét meg kell adni a program írásakor.

Helytelen:
scanf("%d", &db);
int tomb[db];
Helytelen:
/* „elég nagy” */
int tomb[];

A C nyelv újabb változata (C99) elfogadja azt, ha a tömb méretét változóval adjuk meg, mint fent a scanf()-es példában. A régebbi, C89-es, illetve C90-es változatban ez még nem volt lehetséges. Ez más programnyelvek (Pascal, Java, …) esetén is eltérően szokott működni. Erről később részletesen lesz szó – egyelőre a tömbökre, mint fix méretű tárolókra gondoljunk. A nem megadott méretű tömb (üres szögletes zárójel) viszont nem működik semelyik fordítóval, és így nagyon súlyos hibának számít!

22 Tíz darab szám – és fordítva

1. szám: 1.23
2. szám: 3.14
3. szám: 5

…

3. szám: 5
2. szám: 3.14
1. szám: 1.23
#include <stdio.h>

int main()
{
   double szamok[10];
   int i;

   /* beolvasás */        // 0-tól 9-ig
   for (i=0; i<10; i+=1) {
      printf("%d. szám: ", i+1);
      scanf("%lf", &szamok[i]);
   }
   /* kiírás */           // 9-től 0-ig
   for (i=9; i>=0; i-=1)
      printf("%d. szám: %f\n", i+1, szamok[i]);

   return 0;
}

A fenti megoldásban mindig hozzáadunk egyet a tömbindexhez, amikor a felhasználónak szóló szövegben a sorszámot hivatkozzuk. Így a programban a tömbindexek tartománya 0…9 (ez kötelező, a C nyelv tömbje miatt), de a képernyőn 1…10 látszik.

23 Tételek tömbökön: másolás és összegzés

Tömb másolása

double forras[5]={9, 2, 4, 1, 5}, cel[5];
int i;

for (i=0; i<5; i+=1)
   cel[i]=forras[i];

Természetesen ennek a tételnek is meg lehet adni a pszeudokódját általánosságban:

CIKLUS AMÍG van még szám, ADDIG
    szám = következő elem
    KI: szám
CIKLUS VÉGE

A fenti kód ennek a tételnek a megvalósítása abból a célból, hogy egy tömb tartalmát egy másikba lemásoljuk. A cél tömb legalább akkora kell legyen, mint a forrás tömb, vagyis amennyi elemet másolunk.


Tömb elemeinek összegzése

int tomb[5]={9, 2, 4, 1, 5};
int osszeg, i;

osszeg=0;
for (i=0; i<5; i+=1)
  osszeg+=tomb[i];
printf("Összeg: %d\n", osszeg);

24 Tételek: maximumkeresés tömbön

#include <stdio.h>

int main()
{
   int tomb[5]={5, 9, 3, 1, 2};
   int maxindex, i;

   maxindex=0;
   for (i=1; i<5; i+=1)
      if (tomb[i] > tomb[maxindex])
         maxindex=i;                // a helyét jegyzi meg

   printf("Legnagyobb: tomb[%d]=%d\n",
            maxindex, tomb[maxindex]);

   return 0;
}

A legnagyobb szám helyére (indexére) érdemes építeni a keresést! Vagyis nem azt nyilvántartani a változóban, hogy mi volt a maximum értéke, hanem hogy hol van a legnagyobb szám, amit eddig láttunk. Ez van a maxindex nevű változóban. Ha a helyet tudjuk, akkor bármikor meg lehet nézni az értéket is. Ha azonban csak az értéket tudnánk, akkor újra meg kellene keresni, hol volt a tömbben az a szám – ha felmerülne ez a kérdés.

25 Tételek: kiválogatás két tömbbe

Negatívak az egyik tömbbe, nem negatívak a másikba.

int szamok[20]={... a számok ...};

int neg[20], nemneg[20];      // ezekbe válogatja szét
int db_neg, db_nemneg, i;

db_neg=0;
db_nemneg=0;
for (i=0; i<20; i+=1)         // összes elem
   if (szamok[i]<0) {
      neg[db_neg]=szamok[i];       // ha igaz rá, hogy…
      db_neg+=1;
   } else {
      nemneg[db_nemneg]=szamok[i]; // ha nem igaz
      db_nemneg+=1;
   }

printf("%d nemnegativ, %d negativ.\n", db_nemneg, db_neg);

Ez az algoritmus egy adott tulajdonság szerint szétválogatja a tömb elemeit. Amelyek rendelkeznek egy bizonyos tulajdonsággal (itt: negatívak), azokat bemásolja az egyik tömbbe, a többit pedig a másikba. Az eredeti tömb változatlanul marad.

A két cél tömb mérete ugyanakkora, mint az eredeti tömbbé, hiszen előfordulhat, hogy az utóbbiban pl. csak negatív számok vannak. Minden egyes esetben, amikor valamelyik tömbbe beírunk egy elemet, akkor az ahhoz a tömbhöz tartozó számlálót megnöveljük. Először így a 0. indexű helyre kerül az elem, utána az 1. indexűre és így tovább. A két számláló így egyben azt is tartalmazza, hogy az egyes tömbökbe hány elem került – azaz hogy hány negatív és hány nemnegatív elem volt. A db_neg+db_nemneg összeg a ciklus lefutása után értelemszerűen az eredeti tömb méretével egyezik meg, mert mindegyik számnak kerülnie kellett valahova.

26 Tömbök vs. nem tömbök

Tipp: emlékezni
kell az összes
elemre → tömb

Tömb használata: példák

  • Fordított sorrendű kiírás
  • Növekvő sorrendbe rendezett kiírás
  • Átlagnál nagyobb számok kiírása
    • Az átlag akkor derül ki, ha megvan az összes
    • Ha megvan az átlag, csak akkor tudjuk, ki kell-e írni a legelsőt
    • Ahhoz viszont emlékezni kell rá, mi volt az

Kell tömb vagy nem kell?

Programozási hibák

28 Kontraszt

kifejezésjelentés
3.14
3, 14
tizedespont, a π közelítő értéke
vessző, elválasztás, pl. pow(3, 14)=314
x==y
x=y
vizsgálat: x egyenlő-e y-nal
értékadás: x legyen egyenlő y-nal
x=b
x='b'
x vegye fel a b változó értékét
x legyen a b betű karakterkódja
x=1
x='1'
x legyen 1
x legyen az 1-es számjegy karakterkódja
printf("%d", 65)
printf("%c", 65)
írd ki 65-öt, mint szám: "65"
írd ki a 65-ös kódú karaktert: "A"
"a"
'a'
szöveg (sztring), amely egy betűt tartalmaz
egyetlen karakter


If blue is the sky…

Az értékadás és az egyenlőségvizsgálat keverése miatt gyakran szokták tanácsolni, hogy a feltételeket fordítva írjuk. Így nem lehet összekeverni a kettőt, hiszen ebben a kódrészletben = értékadás használata esetén szintaktikai hibát kapunk, ami fordítási hibához vezet. Sajnos a fordított feltétel az olvashatóságot csökkenti, néha zavar a kód megértésében. Ezért mi nem javasoljuk a használatát. A programozó folklór egyébként ezt a stílust Yoda-feltételnek nevezi, mert Yoda az eredeti Csillagok háborúja szinkronban így beszél (pl. „if blue is the sky”-t mond „if the sky is blue” helyett.)

29 A fordítók figyelmeztetései

A Code::Blocks Settings menüjében találunk egy Compiler and debugger… menüpontot. Ezt megnyitva a fordítóprogram beállításaihoz jutunk. A fent látható két opciót nagyon erősen javasolt engedélyezni az otthoni gépeteken. Ilyenkor ugyanis a fordító minden gyanús, szokatlan kódrészletre figyelmeztetést ad (-Wall), illetve a nem szabványos, esetleg más fordítókkal nem működő nyelvi fordulatokat nem engedi használni (-pedantic). Ezeket beállítva sokkal hatékonyabb, könnyebb a tanulás és a gyakorlás! Például az előbb említett x=y és x==y összekeverésére is a legtöbbször figyelmeztetni tud.

30 Néhány szó a kezdeti értékekről

double pi = 3.14;       // inicializált változó

int i, osszeg;          // inicializálatlan változó

Ha nem kap kezdeti értéket, inicializálatlan leszmemóriaszemét


A lentiek jelentik azt, hogy görcsösen adjunk kezdeti értéket minden változónak, akkor is, ha felesleges! Például egy ciklusváltozót felesleges inicializálni a létrehozásakor, hiszen a for() fejlécében úgyis fog értéket kapni. Egy összegzést végző programrész osszeg=0 utasítását is érdemes a ciklus elé tenni közvetlenül, hiszen ahhoz a programrészlethez tartozik logikailag!

Coding Horror logo

Az inicializálatlan változók

Ezt a kódrészletet mindenki ki tudja próbálni a saját gépén. A legmeglepőbb dolgok történhetnek, mivel a működése az inicializálatlan változók miatt nem definiált.

#include <stdio.h>

int main()
{
   int t[15];  /* inicializálatlan */
   int i;

   for (i=0; i<15; i+=1)
      printf("%d\n", t[i]);

   return 0;
}

31 Tömbök túlindexelése

Túlindexelés

Az indexhatárok nem ellenőrzésének az oka egyszerű: a hatékonyság. A C nyelv sok modern programozási nyelvvel ellentétben arra való, hogy a lehető leggyorsabban futó programokat írjuk vele. A helyesen megírt programban sehol nincsen tömb túlindexelés, ezért felesleges is lenne a program futása közben ellenőrizni, hogy van-e benne ilyen hiba! A helyes program írása így aztán nem másnak a feladata, mint a programozónak.


Ördög

„Ki itt belépsz, hagyj fel minden reménnyel.”

Nem definiált működés: ha valami nincs meghatározva a C szabványban, akkor annak hatására akármi is történhet. Nincs garantálva semmi.

Ezt is érdemes kipróbálni.

#include <stdio.h>

int main()
{
   double t[10];
   int a=1, b=2, c=3;

   printf("a=%d\nb=%d\nc=%d\n", a, b, c);

   /* túlindexelés */
   t[-1]=0.2;
   t[10]=0.3;
   printf("\n");

   printf("a=%d\nb=%d\nc=%d\n", a, b, c);

   return 0;
}