Programozási tételek
Tartalom
- Emlékeztető: feladatok és eszközök
- Az állásinterjús kérdés: fizz buzz
- Fizz buzz: 3-mal és 5-tel is
- Programozási tételek
- Sorozatok és tételek
- Összegzés tétele
- Összegzés? Faktoriális!
- Számlálás tétele: osztók száma
- A karakter típus – feladat
- A karakterek kezelése C-ben
- Számlálás tétele: „e” és „E” betűk
- Szélsőérték keresése: a leg…
- Lineáris keresés
- A logikai típus C-ben
- Lineáris keresés: prímszám-e (C kód)
- Tételek kombinációja: a klasszikus példa
- Tömbök
- Tíz darab szám
- A tömb
- A tömbök kezelése I. – hogyan
- A tömbök kezelése II. – hogyan ne
- Tíz darab szám – és fordítva
- Tételek tömbökön: másolás és összegzés
- Tételek: maximumkeresés tömbön
- Tételek: kiválogatás két tömbbe
- Tömbök vs. nem tömbök
- Programozási hibák
- Kontraszt
- A fordítók figyelmeztetései
- Néhány szó a kezdeti értékekről
- Tömbök túlindexelése
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
- 3 többszöröse, a szám helyett: „fizz”
- 5 többszöröse, akkor „buzz”
- mindkettőé, akkor „fizzbuzz”
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 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.
#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.
5 Sorozatok és tételek
Sorozatok (nem a Dallas)
- Számsorok
- Elemszám kétféleképpen lehet adott:
- Adott hosszúságú. A sorozat hossza előre adott.
Pl. 4 elem: 9, 1, 3, 5 - Végjeles. A sorozat végét egy speciális érték
jelöli.
Pl. 9, 1, 3, 5, -1
- Adott hosszúságú. A sorozat hossza előre adott.
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.
Ö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)
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!
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.)
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{|}~
- Minden betűhöz, számjegyhez, írásjelhez egy kódszámot rendelnek
- Többféle kódolás létezik, gyakori a Unicode, ASCII („eszki”, American Standard Code for Information Interchange)
- Pl.
A
→65,a
→97,!
→33,0
→48, de vezérlő kódok is: sortörés (\n), oldaltörés stb. - Nem kell tudni fejből a kódszámokat!
- A számjegyek és a betűk sorban vannak
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);
%c
: azt a karaktert írja ki, amelynek a kódja a megadott szám- A gépnek szám (belső ábrázolás), nekünk betű (külső ábrázolás)
11 Számlálás tétele: „e” és „E” betűk
„vagy”: valamelyik
feltétel teljesül
(elég az egyik)
#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
- Lehetséges értékei: IGAZ, HAMIS
- Műveletek: és, vagy, tagadás stb.
- C-ben nincs hozzá külön típus, helyette egész számot használ:
int
- A 0 érték a HAMIS, a nem 0 (minden más) az IGAZ
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
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; }
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.
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
a0 | a1 | a2 | a3 | a4 | a5 |
---|---|---|---|---|---|
99 | 71 | 3 | -45 | 47 | 12 |
Szóhasználat
- Egyszerű/beépített adattípusok: egész, valós, karakter, …
- Összetett/származtatott adattípus: pl. a tömb (több egészből)
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!
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.
int a[10], b[10]; a=b;
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.
scanf("%d", &db); int tomb[db];

/* „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
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?
- Tipikus hiba tömböt használni, amikor nincs rá szükség
- Pl. hasraütésszerűen 1000 elemű tömbbe beolvasott számsor
- Összeg, keresés, szélsőérték: ezekhez nem kell,
- Nem kell emlékezni a régiekre és sorrendben kell feldolgozni őket
28 Kontraszt
kifejezés | jelentés |
---|---|
3.14 |
tizedespont, a π közelítő értéke vessző, elválasztás, pl. pow(3, 14) =314 |
x==y |
vizsgálat: x egyenlő-e y-nal értékadás: x legyen egyenlő y-nal |
x=b |
x vegye fel a b változó értékét x legyen a b betű karakterkódja |
x=1 |
x legyen 1 x legyen az 1-es számjegy karakterkódja |
printf("%d", 65) |
írd ki 65-öt, mint szám: "65" írd ki a 65-ös kódú karaktert: "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

- Enable all compiler warnings (-Wall)
- Enable warnings demanded by strict ISO C (-pedantic)
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 lesz → memó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!

Az inicializálatlan változók
- Súlyos hiba egy inicializálatlan változó értékét használni!
- Lehet, hogy nulla, lehet, hogy nem
- Lehet, hogy mindig más az értéke, lehet, hogy nem
- Lehet, hogy működik a program, lehet, hogy nem
- Misztikus, követhetetlen hibajelenségek!
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
- A C nem ellenőrzi a megadott tömbindexeket
int t[10]
tömb túlindexelése:t[-1], t[10], t[234]
- Helyes indexek:
t[0]…t[9]
- Helyes indexek:
- Misztikus hibák, elvesző változóértékek, lefagyó programok
- Ezért a tömbök túlindexelése is súlyos hiba!
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.
„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; }